Search code examples
rshinyshiny-reactivity

How to make reactivity work in a base dataframe in R Shiny?


In the below code, I am trying to multiply the base dataframe baseDF by the slider input value input$svc, just like I do in mod1_server with line of code df * as.numeric(svc). However, as the code is drafted the App crashes with message Warning: Error in $: Can't access reactive value 'svc' outside of reactive consumer.ℹ Do you need to wrap inside reactive() or observe()? What am I doing wrong?

For learning purposes, I'm trying to keep the baseDF definition (baseDF <- data.frame(A = 1:2, B = 3:4)) outside of any reactive expression, but would like to learn how to bring it in to reactivity through alternate means.

If you comment out that troublesome line of code, and uncomment the line immediately beneath it, you'll see that the code works fine EXCEPT for multiplying the baseDF by the slider input.

Code:

library(shiny)
library(DT)

mod1_ui <- function(id) {
  ns <- NS(id)
  DTOutput(ns("tbl"))
}

mod1_server <- function(id, common) {
  moduleServer(id, function(input, output, session) {
    new_dat <- reactive({
      df <- common$df
      svc <- common$svc()
      df * as.numeric(svc)
    })
    observe({common$mod1_dat <- new_dat()})
    output$tbl <- renderDT({new_dat()})
  })
}

mod2_ui <- function(id) {
  ns <- NS(id)
  DTOutput(ns("tbl"))
}

mod2_server <- function(id, common) {
  moduleServer(id, function(input, output, session) {
    output$tbl <- renderDT({
      dat <- data.frame(common$mod1_dat + 10) 
      common$mod2_table <- dat
      dat
    })
  })
}

ui <- fluidPage(
  mainPanel(
    h5(strong("Base table:")),
    DTOutput("table"),
    sliderInput("svc", "", min = 0, max = 10, value = 1),
    mod1_ui("mod1"),
    mod2_ui("mod2")
  )
)

server <- function(input, output, session) {
  baseDF <- data.frame(A = 1:2, B = 3:4) # keep this outside of a reactive
  common <- reactiveValues(df = baseDF * input$svc) # ??? HELP!!! CRASHES
  # common <- reactiveValues(df = baseDF) # ??? But this works, but needs to have input$svc
  output$table <- renderDT({datatable(common$df)})
  common$svc <- reactive(input$svc)
  mod1_server("mod1", common)
  mod2_server("mod2", common)
  
  observe({common_tmp <<- shiny::reactiveValuesToList(common)})
  
}

shinyApp(ui, server)

Solution

  • The line common <- reactiveValues(df = baseDF * input$svc) just initialises the reactiveValues object, but is not reactive itself. Therefore, it can't deal with the input$svc.

    In order to update a reactiveValues object, you need to use observe/observeEvent (which is a reactive consumer):

    common <- reactiveValues(df = baseDF)
      observeEvent(input$svc, {
        common$df <- common$df * input$svc
      })