Sooji
Sooji

Reputation: 169

R Shiny module not updating reactively within same event

I'm having an issue with reactivity when using modules in R. If I update a module and then try to update another module with those updated values, I instead get the values prior to the update.

I've written up some basic code to show what I mean below. Here I have an app that updates a rHandsontableOutput placed in a module called my_module and then copies this updated rHandsontableOutput to a second module called module_to_update when a button is pressed.

What I'm finding is that the first table in my_module will update but not the one in module_to_update. Instead, the module_to_update table will receive a copy of my_module's initial table prior to the update. If I press the update button again, things work as expected.

I'm guessing this is an issue with either how I'm handling the session or reactive values generally, but I'm out of ideas.

QUESTION: How can I set up reactive values and modules such that I can run operations on updated module data within the same function call? (e.g. see the observeEvent(input$update_btn, ...) call below for an example)

Image: Updated table not getting copied

application.R

library(shiny)
library(rhandsontable)

source('my_modules.R')

active_tab = ""

ui <- navbarPage("Module Test Tool",

             tabsetPanel(id = 'mainTabset',
                         tabPanel("My Tab",

                                  #This is the HoT that works as expected, updating when called upon
                                  h4("Table 1"),
                                  myModuleUI('my_module'),
                                  #This is the HoT that does NOT work as expected. This HoT fails to use the updated values from 'my_module' HoT
                                  h4("Table to be updated"),
                                  myModuleUI('module_to_update'),
                                  br(),
                                  br(),

                                  fluidRow(
                                    #this button updates tables to new values
                                    actionButton("update_btn", "Update and Add Tables"),
                                    br(),
                                    br(),
                                    textOutput('table1_sum'),
                                    textOutput('table2_sum'),
                                    br(),
                                    br()
                                  )
                         )
             )

)


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

  #Link logic for tab module
  callModule(myModule, 'my_module')



  #This button sums up all the rHandsonTable data frames
  observeEvent(input$update_btn, {


    #Update values in table and integer drop down list before doing basic operations on them
    #New values should be all 5s
    five_col = rep(5,3)
    callModule(updateModule, 'my_module', 5, data.frame(col1 = five_col,
                                                          col2 = five_col,
                                                          col3 = five_col))



    #Grabs updated module table and does operations on it
    module_data = callModule(getMyModuleData, 'my_module')
    module_int= module_data$module_int
    module_df = module_data$module_df

    output$table1_sum = renderText({
      paste0("Sum of Table 1 is: ", sum(module_df())," | The selected integer is: ", module_int())
    })

    #------------------------------------------------------
    #------------------ERROR BELOW-------------------------
    #------------------------------------------------------
    #THIS IS THE CODE THAT FAILS. This updates a 2nd module that should mirror the updated values. However, this results in old values.
    callModule(updateModule, 'module_to_update', module_int(), module_df())

    #Tries to call on new, updated table
    updated_module_data = callModule(getMyModuleData, 'module_to_update')
    updated_module_int= updated_module_data$module_int
    updated_module_df = updated_module_data$module_df

    #Display results of basic operations on new table
    output$table2_sum = renderText({
      paste0("Sum of Updated Table is: ", sum(updated_module_df())," | The selected integer is: ", updated_module_int())
    })
  })


}

## Create Shiny app ----
shinyApp(ui, server)

my_modules.R



#Simple module containing one rHandsontable and a drop down list of integers
myModuleUI <- function(id,tab_name){

  ns <- NS(id)

  fluidRow(
    rHandsontableOutput(ns("module_hot")),
    selectInput(ns('module_int_list'),"Integers:",c(1:5), selected = 1)
  )


}

#Initializes myModuleUI rHandsonTable with some values
myModule <- function(input, output, session) {

  one_col = rep.int('VALUE AT INITIALIZATION',3)
  df = data.frame(col1 = one_col,
                  col2 = one_col,
                  col3 = one_col)

  output$module_hot <- renderRHandsontable({
    rhandsontable(df, stretchH = "none", rowHeaders = NULL)
  })
}

#Returns myModule data for use outside of the module
getMyModuleData <- function(input,output,session){

  return (
      list(
        module_df = reactive({hot_to_r(input$module_hot)}),
        module_int =  reactive({input$module_int_list})
    )
  )
}

updateModule<- function(input,output,session, new_integer, new_dataframe){
  if(!is.null(new_dataframe))
  {
    output$module_hot <- renderRHandsontable({
      rhandsontable(new_dataframe, stretchH = "none", rowHeaders = NULL)
    })
  }

  outputOptions(output, "module_hot", suspendWhenHidden = FALSE)

  updateSelectInput(session, "module_int_list",selected = new_integer)
}


Upvotes: 3

Views: 1682

Answers (1)

Adam Sampson
Adam Sampson

Reputation: 2011

There are a few problems in here...

You are calling multiple different modules with the same namespace. Modules are supposed to operate independently of each other. They should each have their own namespace. The following are not correct:

callModule(myModule, 'my_module') 

callModule(updateModule, 'my_module', 5, data.frame(col1 = five_col,
                                                          col2 = five_col,
                                                          col3 = five_col))

module_data = callModule(getMyModuleData, 'my_module')

You are calling modules from within observeEvent(). This means every time you observe that event you try to initialize that module. You don't want to initialize the module, you want to pass the new variables to that module. If you make a module return it's values, then use those returned values as inputs into another module you won't need to observe the event...the module that receives the new information will decide whether to observe the change.

You have created a function/module getMyModuleData that is only supposed to return data that is present in a different module. Instead you should have the other module return the data you want.

Check out: https://shiny.rstudio.com/articles/communicate-bet-modules.html.

Upvotes: 6

Related Questions