Missing reactive dependency in shiny app stopping data from updating with input

285 views Asked by At

I am trying to write a shiny app that will let the user;

  1. Load some data in,
  2. Select a specific row based off an ID,
  3. Edit the data in a datatable, and
  4. Export the edited data

As it is, the app does all of these things, but does not allow the user to change the ID and select a new row without restarting the app. The data in the table stays the same and doesn't update the ID input is changed or when the action button is pressed.

I think the issue is that I am missing a reactive dependency somewhere, but I am not sure where it is.

library(shiny)
library(DT)
library(dplyr)

editTableUI <- function(id, width = NULL) {
  ns <- NS(id)
  tagList(fluidRow(DT::dataTableOutput(ns('data_table'), width = width)))
}

editTableServer <-
  function(input, output, session, data) {
    
    output$data_table = DT::renderDataTable(
      data,
      selection = 'none',
      editable = TRUE,
      options = list(dom = 't',
                     pageLength = nrow(data)))
    
    proxy = DT::dataTableProxy('data_table')
    
    observeEvent(input$data_table_cell_edit, {
      info = input$data_table_cell_edit
      str(info)
      i = info$row
      j = info$col
      v = info$value
      
      data[i, j] <<- coerceValue(v, data[i, j])
      replaceData(proxy, data, resetPaging = FALSE)
      }
    )
    return({reactive(data)})
  }


#  ------------------------------------------------------------------------

ui <- fluidPage(
  uiOutput("id"),
  conditionalPanel(condition = "input.id",
                   actionButton(inputId = "go_id", label = "Load ID Data")),
  editTableUI("table"),
  downloadButton('download_CSV', 'Download CSV')
)


server <- function(input, output, session) {

# Load data ---------------------------------------------------------------

  df <- reactive({iris %>% mutate(id = rownames(iris))})
  
  # create list of IDs
  output$id <- renderUI({
    id_list <- df() %>% pull(id)
    selectInput("id", "Select an ID", choices = id_list, multiple = F)})
  
  # filter total data to data for selected ID
  id_df <- eventReactive(input$go_id, {df() %>% filter(id == input$id)})
  
  # select variables and gather
  display_df <- eventReactive(input$go_id,{
    id_df() %>% 
      select(-Species) %>% 
      tidyr::gather(key = "Variable Label", value = "Original") %>%
      dplyr::mutate(Update = as.numeric(Original))})
  
  
  editdata <- callModule(editTableServer, "table", data = display_df())
  
  
  output$download_CSV <- downloadHandler(
    filename = function() {paste("dataset-", Sys.Date(), ".csv", sep = "")},
    content = function(file) {write.csv(editdata(), file, row.names = F)})
             
}

shinyApp(ui, server)
1

There are 1 answers

2
henryn On BEST ANSWER

I think the issue is arising where you are passing a reactive to callModule.

If you change your callModule in the server to this:

editdata <- callModule(editTableServer, "table", data = display_df)

and change your renderer in the module to this:

output$data_table = DT::renderDataTable(
      data(),
      selection = 'none',
      editable = TRUE,
      options = list(dom = 't',
                     pageLength = nrow(data)))

(i.e with parentheses after data to execute the reactive) then you should get the behaviour that you want.

What was happening in your initial implementation was that you were executing the reactive in callModule, so data is just a static data frame. No reactive dependency is established between anything in your module and the inputs in the rest of the app, so the table didn't update. In general (perhaps not always, but most of the time), you should pass reactives to modules just as they are without parentheses, and call them with parentheses to get their values in the relevant reactive context in your module code.

I'd suggest you also review your implementation of the data update in the observeEvent as super assigns (<<-) in Shiny apps are generally not a good idea, and this is going to break anyway as data has now been passed to the module as a reactive.

I'm not entirely clear on what your intention is here, but if you just want the module to return the edited data, then something like this might be more appropriate than the existing observeEvent:

edit_data <- eventReactive(input$data_table_cell_edit, {
      info = input$data_table_cell_edit
      str(info)
      i = info$row
      j = info$col
      v = info$value
      data <- data()
      data[i, j] <- coerceValue(v, data[i, j])
      replaceData(proxy, data, resetPaging = FALSE)
      data
    })

Then you would return(edit_data)out of your module.

Hope that was helpful.