Search code examples
rshinyshinymodules

How to pass value from one instance of shiny module to another instance of the same module?


I have created a contrived example of what I want to achieve. Basically, I have a modular shiny app with two tabs, Region and District. In the region tab, I want to display a table with the dataset dependent on the region selected in the dropdown menu. In the district tab I want to display a table again with the dataset dependent on both the region selected previously and the district selected in the dropdown menu of the district tab. The district displayed in the selectInput of the district tab should be dependent on the region selected in the Region tab

However, on trying to get the region selection in the region tabs dropdown menu to be accessible in the district tabs processing of what data to display is not working, I am sure due to scoping issues with modules. But this is a common enough workflow for me to believe there is an answer, and here I am. The code is as below:

UPDATED CODE TO CREATE LOCATIONS OBJECT FOR CLARITY

library(shiny)
library(shinydashboard)
#> 
#> Attaching package: 'shinydashboard'
#> The following object is masked from 'package:graphics':
#> 
#>     box
library(tidyr)

locations = tribble(
  ~region,~district,
  "Morogoro","Morogoro DC",
  "Morogoro","Gairo DC",
  "Lindi","Tandahima DC",
  "Lindi", "Kilwa DC"
  )

get_regional_dataset = function(region){
  #browser()
  if(region=="Morogoro"){
    mtcars
  }else{
    iris
  }
}

get_district_dataset = function(region,district){
  #browser()
  if(region=="Morogoro" & district=="Morogoro DC"){
    mtcars
  }else{
    iris
  }
}

table_UI1 <- function(id) {
  ns <- NS(id)
  tagList(
    
    sidebarPanel(width = 2,
                 uiOutput(ns("selector")),
    )
    
  )
}

table_UI2 <- function(id) {
  ns <- NS(id)
  tagList(
    
    mainPanel(
      DT::dataTableOutput(ns('table'))
    )
    
  )
}

table_Server1 <- function(id) {
  moduleServer(id,function(input, output, session) {
    ns <- session$ns
    output$selector <- renderUI({
      if(id %in% c("ER_district")) {
        #browser()
        choices <- locations$district 
        label <- "Council"
      }
      else { 
        choices <- locations$region
        label <- "Region"
      }
      
      selectInput(inputId=NS(id,"choice"),
                  label = label,
                  choices = choices)
    })
    return(reactive(input$choice))
  })
}

table_Server <- function(id, mychoice, mychoice2) {
  moduleServer(id,function(input, output, session) {
    rv <- reactiveValues()
    
    observe({
      
      rv$reg <<- mychoice()
      rv$dist <<- mychoice2()
      
      if (!is.null(mychoice())) rv$df <- get_regional_dataset(rv$reg)
      if(id=="ER_district"){
        if (!is.null(mychoice2())) rv$df <- get_district_dataset(rv$reg,rv$dist)
        
      }
    })
    
    output$table = DT::renderDataTable({
      rv$df
    })
    
  })
}

ui = fluidPage(
  tabsetPanel(id = 'cqi_indicators',
              tabPanel('Region',
                       tabsetPanel(
                         id='region_indicators',
                         tabPanel("Early Retention",table_UI1("ER"), table_UI2("ER"))
                       )
              ),
              tabPanel('District',
                       tabsetPanel(
                         id='district_indicators',
                         tabPanel("Early Retention",table_UI1("ER_district"), table_UI2("ER_district"))
                       )
              )
              
  )
)

server = function(input,output,session){
  
  choice1 <- table_Server1("ER")
  choice2 <- table_Server1("ER_district")
  
  table_Server("ER", choice1, choice2)
  table_Server("ER_district", choice1, choice2)
  
}

shinyApp(ui,server)
#> PhantomJS not found. You can install it with webshot::install_phantomjs(). If it is installed, please make sure the phantomjs executable can be found via the PATH variable.
Shiny applications not supported in static R Markdown documents

Created on 2023-06-27 by the reprex package (v2.0.1)


Solution

  • In your case it is better to have a separate module for the second selectInput as it depends on first selection of region. Try this

    locations = tribble(
      ~region,~district,
      "Morogoro","Morogoro DC",
      "Morogoro","Gairo DC",
      "Lindi","Tandahima DC",
      "Lindi", "Kilwa DC",
      "Lindi", "Dummy DC"
    )
    
    library(shiny)
    library(shinydashboard)
    
    get_regional_dataset = function(region){
      #browser()
      if(region=="Morogoro"){
        mtcars
      }else{
        iris
      }
    }
    
    get_district_dataset = function(region,district){
      #browser()
      if(region=="Morogoro" & district=="Morogoro MC"){
        mtcars
      }else{
        iris
      }
    }
    
    table_UI1 <- function(id) {
      ns <- NS(id)
      tagList(
    
          sidebarPanel(width = 2,
                       uiOutput(ns("selector")),
          )
    
      )
    }
    
    table_Server1 <- function(id) {
      moduleServer(id,function(input, output, session) {
        ns <- session$ns
        output$selector <- renderUI({
          choices <- unique(locations$region)
          label <- "Region"
    
          selectInput(inputId=NS(id,"choice"),
                      label = label,
                      choices = choices)
        })
        return(reactive(input$choice))
      })
    }
    
    table_UI2 <- function(id) {
      ns <- NS(id)
      tagList(
        
        sidebarPanel(width = 2,
                     uiOutput(ns("selector2")),
        )
        
      )
    }
    
    table_Server2 <- function(id,choice_reg) {
      moduleServer(id,function(input, output, session) {
        ns <- session$ns
        output$selector2 <- renderUI({
          df <- locations[locations$region==choice_reg(),]
          choices <- unique(df$district)
          label <- "Council"
          selectInput(inputId=NS(id,"choiced"),
                      label = label,
                      choices = choices)
        })
        return(reactive(input$choiced))
      })
    }
    
    table_UI <- function(id) {
      ns <- NS(id)
      tagList(
        mainPanel(
          DT::dataTableOutput(ns('table'))
        )
      )
    }
    
    table_Server <- function(id, mychoice, mychoice2) {
      moduleServer(id,function(input, output, session) {
        rv <- reactiveValues()
    
        observe({
          if (!is.null(mychoice())) rv$df <- locations[locations$region == mychoice(),] # get_regional_dataset(mychoice())
          if(id=="ER_district"){
            if (!is.null(mychoice2()))  rv$df <- locations[locations$region == mychoice() & locations$district == mychoice2(),] ## get_district_dataset(mychoice(), mychoice2())
            print(mychoice2())
            print(mychoice())
          }
        })
    
        output$table = DT::renderDataTable({
          rv$df
        })
    
      })
    }
    
    ui = fluidPage(
      tabsetPanel(id = 'cqi_indicators',
                  tabPanel('Region',
                           tabsetPanel(
                             id='region_indicators',
                             tabPanel("Early Retention",table_UI1("ER"), table_UI("ER"))
                           )
                  ),
                  tabPanel('District',
                           tabsetPanel(
                             id='district_indicators',
                             tabPanel("Early Retention",table_UI2("ER_district"), table_UI("ER_district"))
                           )
                  )
    
      )
    )
    
    server = function(input,output,session){
    
      choice1 <- table_Server1("ER")
      choice2 <- table_Server2("ER_district",choice1)
      table_Server("ER", choice1, choice2)
      table_Server("ER_district", choice1, choice2)
      
    }
    
    shinyApp(ui,server)