3

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)
}


Sooji
  • 169
  • 3
  • 18
  • Why have you separated rhandsontable rendering and data evaluation into multiple modules? Seems to complicate things quite a lot. Could you provide a minimal example pointing out your problem? It's very hard to follow your code structure – Thomas Aug 15 '19 at 15:54
  • Hi Thomas, thanks for the feedback. I can likely strip this down further. To give you context though, this is a very simplified version of my actual problem. My tool has modularized tab panels which have rhandsontables in them. I am performing an operation that updates an rhandsontable in one module, and then uses the updated values in a separate module. This was my attempt to recreate the problem in simple terms. – Sooji Aug 15 '19 at 17:40

1 Answers1

6

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.

Adam Sampson
  • 1,971
  • 1
  • 7
  • 15
  • Thanks Adam-- my approach of using modules like a class/object in a object oriented language seems wrong. In that sense, it's as if I'm trying to "set" the `my_module` module using `updateModule` and then "get" the parameters with `getMyModuleData`. You say "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." I read through that article but am still unsure. Any recommendations on how to do this outside an observe? – Sooji Aug 18 '19 at 18:20
  • 1
    Gotcha. In this case modules should be treated as different programs. You create an instance of the module (program) by giving it a namespace (unique id) when you start it. This enables you to create multiple instances of the same module (but each instance is a completely different program). Modules (aka programs) can communicate back and forth with each other through inputs and returns. The main `app.r` is in charge of initializing each module one time, and it is in charge of taking return values from one module and passing them as inputs to another module. – Adam Sampson Aug 19 '19 at 13:42