Salut! I wanted to share a little project I recently worked on that builds upon a great post from Appsilon: Infinite Scroll in R Shiny).
In their post, Appsilon demonstrates how to implement infinite scrolling in R Shiny. I’ve taken it a step further by adding a search input feature and modularizing the code, making it more reusable for different applications. I hope this addition helps streamline your Shiny projects!
Let’s dive into the code.
First things first #
Before using the module, make sure to include shinyjs::useShinyjs() and waiter::useWaiter() in your code. On the server side, you’ll need to set up your reactive values with something like aux <- shiny::reactiveValues(…).
Personally, I find it useful to always have a reactiveValues object handy to pass between modules. This allows me to easily share reactive objects across different parts of the app, making everything more connected and efficient.
Back to the code.
Your app will look like this
ui <- bslib::page_fixed(
shinyjs::useShinyjs(),
waiter::useWaiter(),
# NOTE HERE YOU CAN CUSTOMIZE THE MODULE IN THE PAGE AS YOU WISH
shiny::div(
style = "width: 600px; margin-left: auto; margin-right: auto;",
scroll_ui("test")
)
)
server <- function(input, output, session) {
aux <- shiny::reactiveValues(
new_data = readRDS("data/data.rds"),
new_id = NULL
)
scroll_server("test", aux)
}
Module for Infinite Scroll #
In the module I just want to highlight one parte in the server-side.
Search #
This piece of code is where you prepare your dataset or construct your query if you’re connecting to a database.
shiny::observe({
# input <- list(search = "over 4 million")
shiny::removeUI(".container .item", multiple = TRUE)
if (nchar(input$search) == "") {
message("news - no search")
data <- aux$new_data |>
dplyr::arrange(id)
} else {
message("news - with search")
data <- aux$new_data |>
dplyr::mutate(
distance = 1 - stringdist::stringdist(
tolower(input$search),
tolower(headline),
method = "jw"
)
) |>
dplyr::arrange(dplyr::desc(distance))
}
shinyjs::runjs(stringr::str_interp(
"Shiny.setInputValue('${ns}list_end_reached', true, { priority: 'event' })",
list(ns = ns(""))
))
aux$new_data <- data
page_number(1)
}) |>
shiny::bindEvent(
input$search,
ignoreNULL = FALSE,
ignoreInit = TRUE
)
Infinite Scroll #
shiny::observe({
w_loader$show()
offset <- (page_number() - 1) * 10
data <- aux$new_data[(offset + 1):(offset + 10), ]
purrr::map(split(data, seq(nrow(data))), function(.x) {
shiny::insertUI(
selector = paste0("#", ns("end")),
where = "beforeBegin",
ui = shiny::div(
class = "item",
shiny::a(
"data-value" = .x$id,
onclick = "GetIdOnClick(this)",
href = "#",
shiny::h5(.x$headline)
),
shiny::p(class = "text-muted", .x$date)
)
)
})
page_number(page_number() + 1)
Sys.sleep(1)
w_loader$hide()
}) |>
shiny::bindEvent(input$list_end_reached)
Final words #
Download the repository and run the app shiny::runApp()
and change the code as you need.
Voila.