简体   繁体   中英

Shiny reactivity not working in subModules

Because my shiny app has become quite large I've recently put some code into modules (also to reuse the code multiple times in different places). Somehow parts of the code do not work anymore as expected.

In this example I have a module which filters data according to input elements and return a reactive data.frame . In the mainPanel I have a module which creates a dataTable from the filtered data. But the reactivity does not work, when I change the selectInput , the dataTable does not update.

library(shiny)
library(DT)

filtersUI <- function(id) {
  ns <- NS(id)
  selectizeInput(
    ns("Species"), label = "Species",
    choices = levels(iris$Species),
    selected = "virginica"
  )
}

filters <- function(input, output, session, .data) {
  inputs <- reactive({
    list("Species" = input[["Species"]])
  })
  reactive({
    .data[.data$Species %in% inputs()[["Species"]], ]
  })
}

dataTableUI <- function(id) {
  ns <- NS(id)
  DT::dataTableOutput(ns("data.table"))
}

dataTable <- function(input, output, session, .data) {
  output$data.table <- DT::renderDataTable({
    DT::datatable(.data)
  })
}

appUI <- function(id) {
  ns <- NS(id)
  sidebarLayout(
    sidebarPanel(
      filtersUI(ns("filter"))
    ),
    mainPanel(
      dataTableUI(ns("data.table"))
    )
  )
}

app <- function(input, output, session, .data) {
  data.subset <- callModule(filters, "filter", .data = .data)
  callModule(dataTable, "data.table", .data = data.subset())
}

ui <-   fluidPage(
  appUI("app")
)

server <- function(input, output, session) {
  callModule(app, "app", .data = iris)
}

shinyApp(ui, server)

But when copying the code from the subModules into the app module, the code works fine:

library(shiny)
library(DT)
appUI <- function(id) {
  ns <- NS(id)
  sidebarLayout(
    sidebarPanel(
      selectizeInput(
        ns("Species"), label = "Species",
        choices = levels(iris$Species),
        selected = "virginica"
      )
    ),
    mainPanel(
      DT::dataTableOutput(ns("data.table"))
    )
  )
}

app <- function(input, output, session, .data) {
  inputs <- reactive({
    list("Species" = input[["Species"]])
  })
  data.subset <- reactive({
    .data[.data$Species %in% inputs()[["Species"]], ]
  })
  output$data.table <- DT::renderDataTable({
    DT::datatable(data.subset())
  })
}

ui <-   fluidPage(
  appUI("app")
)

server <- function(input, output, session) {
  callModule(app, "app", .data = iris)
}

shinyApp(ui, server)

I know the modular structure looks like overkill in this simple example, but in my real app I have lots of code in the modules, which I deleted to make this example minimal. So it would be nice to have a solution using the same modular structure as in the first code snippet. Any ideas why it fails?

You did a very nice job creating a repoducible example with submodules. However, the issue does in fact not have anything to do with submodules. You just need to pass the reactive object data.subset differently. Instead of

callModule(dataTable, "data.table", .data = data.subset())

you should use

callModule(dataTable, "data.table", .data = data.subset)

to pass the reactive itself rather than its current value. The value can then be "resolved" in DT::renderDataTable like this

output$data.table <- DT::renderDataTable({
    DT::datatable({.data()})                                   
})

The way you coded it, the data at "construction time" ie the unfiltered dataset is sent to the module and it can't be observed along the way.

To be clear: The commented lines ( ## remove parantheses here and ## add parantheses here ) are the only ones I changed from your original code.

library(shiny)
library(DT)

filtersUI <- function(id) {
  ns <- NS(id)
  selectizeInput(
    ns("Species"), label = "Species",
    choices = levels(iris$Species),
    selected = "virginica"
  )
}

filters <- function(input, output, session, .data) {
  inputs <- reactive({
    list("Species" = input[["Species"]])
  })
  reactive({
    .data[.data$Species %in% inputs()[["Species"]], ]
  })
}

dataTableUI <- function(id) {
  ns <- NS(id)
  DT::dataTableOutput(ns("data.table"))
}

dataTable <- function(input, output, session, .data) {
  output$data.table <- DT::renderDataTable({
    DT::datatable({.data()})                                  ## add parantheses here
  })
}

appUI <- function(id) {
  ns <- NS(id)
  sidebarLayout(
    sidebarPanel(
      filtersUI(ns("filter"))
    ),
    mainPanel(
      dataTableUI(ns("data.table"))
    )
  )
}

app <- function(input, output, session, .data) {
  data.subset <- callModule(filters, "filter", .data = .data)
  callModule(dataTable, "data.table", .data = data.subset)    ## remove parantheses here
}

ui <-   fluidPage(
  appUI("app")
)

server <- function(input, output, session) {
  callModule(app, "app", .data = iris)
}

shinyApp(ui, server)

To sum things up, here is a quote from Joe Cheng to a similar issue

Hi Mark, the code in linkedScatter itself is correct; but when calling callModule, you want to pass the reactive itself by name (car_data) without reading it (car_data()).

callModule(linkedScatter, "scatters", car_data)

This is similar to how you can pass a function by name to something like lapply:

lapply(letters, toupper) # works

lapply(letters, toupper()) # doesn't work

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM