Skip to main content
  1. Posts/

Infinite Scroll with Search in R Shiny

·453 words·3 mins
Shiny Infinite-Scroll News Module
Andryas/shiny-scroll-news

R
0
0

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.

If you’re new to R Shiny, there’s a handy way to share data between modules without directly passing it to them. Check out session$userData—it’s a great tool for keeping your app’s data accessible across different modules. đŸ˜‰

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.