I am trying to write a shiny app that will let the user;
- Load some data in,
- Select a specific row based off an ID,
- Edit the data in a datatable, and
- 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)
I think the issue is arising where you are passing a
reactive
tocallModule
.If you change your
callModule
in the server to this:and change your renderer in the module to this:
(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
, sodata
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 asdata
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
:Then you would
return(edit_data)
out of your module.Hope that was helpful.