Search code examples
rshinyreactable

How to update a dataframe in R Shiny with user input


I want to update a dataframe with user input in an R Shiny app. The user selects a row of data, and then chooses the value to update the Species column with using selectInput. The updates need to accumulate each time, i.e., the new updates update the previously updated data. I tried using reactiveValues as per this answer, but couldn't get it to work.

library(shiny)
library(reactable)
library(tidyverse)

iris_df = iris %>% 
  mutate(
    id = row_number(),
    Species = as.character(Species)
    )

ui <- fluidPage(
  
  navbarPage(
    "HELP!", 
    tabPanel(
      "Iris",
      sidebarLayout(
        sidebarPanel(
          selectInput("update_species", "Update species", choices = c("Rose", "Daffodil"))
        ),
        mainPanel(fluidRow(reactableOutput("iris")))
      )
    )
  )
)


server <- function(input, output) {
  
  observeEvent(input$update_species, {
      iris_df = iris_df %>% 
        mutate(Species = case_when(id == selected_row() ~ input$update_species, TRUE ~ Species))
  })
  
  selected_row = reactive(getReactableState("iris", "selected"))
  
  output$iris = renderReactable({
    reactable(
      iris_df,
      selection = "single",
    )
  })
}

shinyApp(ui = ui, server = server)

Solution

  • The first issue with your code is that the observeEvent is triggered by input$update_species which results in an error if no row was selected in which case selected_row() is NULL. To prevent that you could add a req(selected_row()) or use selected_row() to trigger the observeEvent as I do in my code below.

    Next, as you already realized you need a reactiveVal or a reactiveValues to actually update the dataframe inside the observeEvent based on the user choice.

    library(shiny)
    library(reactable)
    library(tidyverse)
    
    iris_df <- iris %>%
      mutate(
        id = row_number(),
        Species = as.character(Species)
      )
    
    ui <- fluidPage(
      navbarPage(
        "HELP!",
        tabPanel(
          "Iris",
          sidebarLayout(
            sidebarPanel(
              selectInput("update_species", "Update species", choices = unique(iris_df$Species))
            ),
            mainPanel(fluidRow(reactableOutput("iris")))
          )
        )
      )
    )
    
    server <- function(input, output) {
      iris_df <- reactiveVal(iris_df)
    
      observeEvent(selected_row(), {
        iris_df(iris_df() %>%
          mutate(Species = case_when(id == selected_row() ~ input$update_species, TRUE ~ Species)))
      })
    
      selected_row <- reactive(getReactableState("iris", "selected"))
    
      output$iris <- renderReactable({
        reactable(
          iris_df(),
          selection = "single",
        )
      })
    }
    
    shinyApp(ui = ui, server = server)
    

    And this is an example output after updating rows 1, 4 and 7:

    enter image description here