Trouble getting reactivity between different modules

I want to do a shiny dashboard where a user is able to load a csv file in a page and to make figures in 2 others. A main page, main, contains a sidebar with the menu and how to navigate between the different pages, interface_input, p1 and p2, all different modules.

I have read a lot of documentation and posts and I don't understand how to "save" the loaded csv in app in order to process the data in the other pages. At first I wanted to stock everything as NULL in a reactiveValues() specified in main and update it along the way, but I can't even load the data into it. I have seen apps where it did work using callModule(), but I wanted to try using moduleServer(), maybe this is not appropriate.

The basic code I use to see if reactivity works is like this :
Main document :

##### Main UI - Sidebar #####
#### Libraries ####
library(shiny)
library(bslib)

#### Import Modules ####
setwd("mypath")
source("Interactive_UI/Interface_input_T.R")
source("Interactive_UI/Page_1.R")
source("Interactive_UI/Page_2.R")

#### Interface ####
ui <- page_sidebar(
  title = "Analysis interface",
  theme = bs_theme(font_scale = 1),
  sidebar = sidebar(
    navset_pill_list(
      id = "menu.modules",
      nav_panel(
        value='upload.data',
        "Charging data"),
      nav_panel(
        value='quantitative.analysis',
        "Test p1",),
      nav_panel(
        value='spatial.analysis',
        "Test p2",),
      selected = 'upload.data'),
    tags$br(),
    actionButton('report', "Export report")),
  uiOutput('uitest')
)

#### Server ####
server <- function(input, output, session) {
  output$uitest <- renderUI ({
      if (input$menu.modules == 'upload.data') {
        return(ui.interface_input('interface'))
      } else if (input$menu.modules == 'quantitative.analysis') {
        return(ui.p1('p1'))
      } else if (input$menu.modules == 'spatial.analysis') {
        return(ui.p2('p2'))}
    })
  

  global_serv <- server.interface_input("interface")
  global.data <- reactiveValuesToList(dtf = NULL, variables = NULL)
  
  observeEvent(input$menu.modules,{
    if(input$menu.modules == 'quantitative.analysis'){
      message("call serv p1")
      server.p1("p1")}
  }, ignoreNULL = TRUE, ignoreInit = TRUE)
  
  observeEvent(input$menu.modules, {
    if(input$menu.modules == 'spatial.analysis'){
      message("call serv spatial")
      server.p2("p2")}
  }, ignoreNULL = TRUE, ignoreInit = TRUE)
}
shinyApp(ui, server)

Interface Input :

ui.interface_input <- function(id) {
  ns <- NS(id)
  page_fixed(
  div(
    style = "position: absolute; top: 10px; right: 20px; z-index: 1000;",
    input_dark_mode()),
  br(),
  titlePanel("Charging data"),
  br(),
  layout_columns(
    card(card_header("Charging data"), 
         card_body(fileInput(ns("file1"), "CSV File",
                             multiple = TRUE,
                            accept = c("text/csv",
                                       "text/comma-separated-values,text/plain",
                                       ".csv")),
                   checkboxInput(ns("header"), "Header", TRUE))),
    layout_columns(
      card(card_header("Choose separator"),
           card_body(radioButtons(ns("sep"), "Separator",
                                  choices = c("Virgule" = ",",
                                              "Point virgule" = ";",
                                              "Tab" = "\t"),
                                  selected = ","))),
      card(card_header("On display"),
           card_body(radioButtons(ns("disp"), "Show",
                                   choices = c(Columns = "col",
                                               Head = "head",
                                               All = "all")))))),
  card(card_header("Table data"),
       card_body(tableOutput(ns("apercu"))
  )))
}

server.interface_input <- function(id) {
  moduleServer(id, function(input, output, session){
    output$apercu <- renderTable({
      req(input$file1)
      
      df <- read.csv(input$file1$datapath,
                     header = input$header,
                     sep = input$sep)
      global.data$dtf <- df
      
      if (input$disp == "col"){
        return(data.frame(t(colnames(df)), check.names=F))
      } else if(input$disp == "head") {
        return(head(df))
      } else {
        return(df)}})
    return(global.data)
    print("saving global.data")
  })
}

So with these 2, the data should be loaded and saved, or so I thought since it didn't work.

Page 1 and page 2 are basically the same, with this structure

ui.p1 <- function(id){
  ns <- NS(id)
  page_fixed(
  "Test interactivity between pages",
  tableOutput('testp1'))
}

# =================== Serveur ====================
server.p1 <- function(id){
  moduleServer(id, function(input, output, session){
    output$testp1 <- renderTable({
      head(global.data$dtf)}) })
}

Except that page 2 shows the tail of the dataframe.

I have also seen apps using several reactiveVal(), but I fear that would be a lot of reactive values always activated and hard to keep track of. Please feel free to tell me if it would be a better option, I have been stuck for a while on that :sweat_smile:

With this set-up I thought the 2 first pages were loading the data in a list, but I seem to be mistaken.
What am I missing? If you think another set up is a better choice, please don't hesitate to tell me, I'll gladly hear you.

Thank you!

Sharing data between modules can be a bit tricky at first. A standard way to handle this is to have your data-loading module return the reactive object itself. You can then capture that return value in your main server and pass it as an argument into your other module server functions (p1 and p2). This keeps the reactivity chain intact and avoids the scoping issues often found with global reactiveValues.

2 Likes

I have changed my code and it works !! Thank you very much ! I have indeed passed the reactive values as arguments in the modules and it works !

I think I've already uncovered some scoping issues while testing my code.

When I change between pages, an alert show_toast appears to show the page server is initiated. But the toast appears not once when the page is initiated, but the same number of times I have passed on the page, which means the server is actually called everytime I pass the pages. That sould be resolvable easily I think by forcing the server to be "closed" when the page is not loaded.

The second issue is that if I modify the dataset in global.data in the module (page 1), it actually modifies it globally. If with a button I locally delete the last column of the dataset, it it also deleted for all the module. In that case I am less certain about the right way to solve the issue and where it might be faulty in the reactive chain.

If you have any idea, please let me know !
Thank you very much again for the help :folded_hands:

1 Like

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.

If you have a query related to it or one of the replies, start a new topic and refer back with a link.