2

I'm trying to use the reactable package to hop to a specific row index after a user modifies the data underlying the reactable.

library(shiny)
library(reactable)
library(dplyr)

ui <- fluidPage(
    selectInput("index", "Index", choices = seq_len(nrow(iris))),
    selectInput("species", "Species", choices = unique(iris$Species)),
    actionButton("go", "Modify"),
    reactableOutput("table")
)

server <- function(input, output, session) {
    # Reactive data
    data <- reactiveValues(
        data = iris |> mutate(Index = row_number())
    )

    # Render reactable
    output$table <- renderReactable({
        reactable(data$data)
    })

    # Update data on button click
    observeEvent(
        input$go,
        {
            # modify the one record
            x <- data$data |>
                filter(Index == input$index) |>
                mutate(Species = input$species)

            # rm from all data
            data$data <- data$data |>
                filter(Index != input$index) |>
                bind_rows(x) |>
                arrange(Index)

            # get index of change
            index <- x |> pull(Index)

            # hop to that approximate page
            updateReactable("table", page = ceiling(index / 10))
        }
    )
}

shinyApp(ui, server)

The observeEvent() successfully updates the data but does not hop to the page of the modified index. I.e., if a user is on page 1 but modifies the record with Index == 44 from setosa to versicolor, I'd like to use reactable::updateReactable() to jump to that page. When running this code, it looks like it tries to jump to the page before the reactable output renders the new data, which is what I'm assuming the issue is.

2 Answers 2

3

The problem is that you update the page right before it gets replaced with the new data. You can use updateReactable to update both data and page at the same time. But you need to isolate inside the renderReactable so you don't update twice (which could be unpredictable).

library(shiny)
library(reactable)
library(dplyr)

ui <- fluidPage(
    selectInput("index", "Index", choices = seq_len(nrow(iris))),
    selectInput("species", "Species", choices = unique(iris$Species)),
    actionButton("go", "Modify"),
    reactableOutput("table")
)

server <- function(input, output, session) {
    # Reactive data
    data <- reactiveValues(
        data = iris |> mutate(Index = row_number()),
    )
    
    # Render reactable, but isolate
    output$table <- renderReactable({
        reactable(isolate(data$data))
    })
    
    # Update data on button click
    observeEvent(
        input$go,
        {
            # modify the one record
            x <- data$data |>
                filter(Index == input$index) |>
                mutate(Species = input$species)
            
            # rm from all data
            data$data <- data$data |>
                filter(Index != input$index) |>
                bind_rows(x) |>
                arrange(Index)
            
            # get index of change, save the needed page
            index <- x |> pull(Index)
            
            # update data and page at same time
            updateReactable("table",data = data$data, page =ceiling(index / 10))
        }
    )
}

shinyApp(ui, server)

Edit: I simplified the code a bit.

1
  • This is helpful, but can cause issues in scenarios where something inside the table must be rendered on each update. In my case, I was trying to use icons in a column and isolate prevented new entries from rendering with icons. An alternative solution is to ensure correct order of operations via reactiveValues paired with htmlwidgets::onRender() stackoverflow.com/a/79186102/21229737
    – sonshine
    Commented Nov 13, 2024 at 17:55
0

isolate() will work in this case, but can cause issues if something in the table requires rendering on each update (i.e., an icon). See this follow-up SO question.

library(shiny)
library(reactable)
library(dplyr)

ui <- fluidPage(
    actionButton("go", "Add Observation"),
    reactableOutput("table")
)

server <- function(input, output, session) {
    # Reactive data
    data <- reactiveValues(
        data = iris |> mutate(Index = row_number()),
        tbl_page = 1
    )

    # Render reactable, but isolate
    output$table <- renderReactable({
        data$data |>
            reactable() |>
            htmlwidgets::onRender(
                "function(el, x) {
                 Shiny.setInputValue('tbl_rendered', Date.now())
                 }"
            )
    })

    # Update data on button click
    observeEvent(
        input$go,
        {
            # create a record to add
            x <- data$data |>
                dplyr::slice_sample(n = 1) |>
                dplyr::mutate(Index = nrow(data$data) + 1)


            # add a record and re id
            data$data <- bind_rows(
                data$data,
                x
            ) |>
                dplyr::arrange(Index)

            # get index of change, save the needed page
            index <- tail(data$data, 1) |> dplyr::pull(Index)

            # update reactiveVals
            data$tbl_page <- ceiling(index / 10)
        }
    )

    # jump to page after table has rendered
    shiny::observeEvent(
        input$tbl_rendered,
        reactable::updateReactable("table", page = data$tbl_page)
    )
}

shinyApp(ui, server)

Not the answer you're looking for? Browse other questions tagged or ask your own question.