-
-
Save sharlagelfand/20646a4c02950c2a0a6d351b70d00a96 to your computer and use it in GitHub Desktop.
options(shiny.reactlog = TRUE) | |
library(shiny) | |
library(reactlog) | |
mod_iris_ui <- function(id) { | |
ns <- NS(id) | |
tagList( | |
fluidRow( | |
column( | |
2, | |
selectInput( | |
inputId = ns("species"), | |
label = "species", | |
choices = list( | |
"loading..." = 1 | |
), | |
selected = 1 | |
) | |
), | |
column( | |
10, | |
plotOutput(ns("speciesplot")) | |
) | |
) | |
) | |
} | |
mod_iris <- function(input, output, session) { | |
ns <- session$ns | |
df <- reactive({ | |
req(ns(input$open_tab) == "iris") | |
df <- iris | |
}) | |
observe({ | |
req(ns(input$open_tab) == "iris") | |
values <- as.character(unique(df()[["Species"]])) | |
updateSelectInput(session, "species", | |
choices = values, | |
selected = values[1] | |
) | |
}) | |
output$speciesplot <- renderPlot({ | |
hist(iris[iris$Species == input$species, 1]) | |
}) | |
} | |
mod_mtcars_ui <- function(id) { | |
ns <- NS(id) | |
tagList( | |
fluidRow( | |
column( | |
2, | |
selectInput( | |
inputId = ns("gear"), | |
label = "gear", | |
choices = list( | |
"loading..." = 1 | |
), | |
selected = 1 | |
) | |
), | |
column( | |
10, | |
plotOutput(ns("gearplot")) | |
) | |
) | |
) | |
} | |
mod_mtcars <- function(input, output, session) { | |
ns <- session$ns | |
df <- reactive({ | |
req(ns(input$open_tab) == "mtcars") | |
df <- mtcars | |
}) | |
observe({ | |
req(ns(input$open_tab) == "mtcars") | |
values <- unique(df()[["gear"]]) | |
updateSelectInput(session, "gear", | |
choices = values, | |
selected = values[1] | |
) | |
}) | |
output$gearplot <- renderPlot({ | |
hist(mtcars[mtcars$gear == input$gear, 1]) | |
}) | |
} | |
ui <- tagList( | |
navbarPage( | |
title = "App", | |
id = "open_tab", | |
tabPanel( | |
"iris", | |
mod_iris_ui("iris") | |
), | |
tabPanel( | |
"mtcars", | |
mod_mtcars_ui("mtcars") | |
) | |
) | |
) | |
server <- function(input, output, session) { | |
callModule( | |
mod_iris, | |
"iris" | |
) | |
callModule( | |
mod_mtcars, | |
"mtcars" | |
) | |
} | |
shinyApp(ui = ui, server = server) |
One other small thing about ns()
, maybe it will be helpful to you. When I first learned about modules ns()
seemed like ✨ magic 🔮. But its main function is to paste()
the module's id in front of any inputs.
So once you've created callModule(mod_iris, "iris", ...)
the name of the input from the selectInput()
is iris-species
. The ns("species")
in selectInput()
adds the module id at the last second, so the input created has the iris-species
id. This is how shiny knows which inputs belong to which module functions.
All inputs with iris-
are seen by the server module (with the iris-
part removed) This is because, on the server side, callModule()
does that work for you with environments to make sure that the server functions only see the inputs within the module. So you only need to wrap input ids in ns()
on the UI side; or if you create UI elements on the server side that end up in the UI with renderUI()
.
I'm not sure if this is helpful but I know that once I took some of the magic out of ns()
I had a better mental model of what was going on.
The first answer is definitely a way to go for more complicated situations.
However, for your example I checked and if you remove all the req() the panels operate independently.
this example wasn't great in illustrating the non-independence and i'm still not 100% clear in my actual case what was causing the tabs to be non-independent. but using req()
and the open_tab helped significantly and ensured that all of the modules run only when their tabs are open. thank you both very much!!!
👋 @sharlagelfand!
I don't know if this completely captures the problem you were seeing but there are a couple changes that can make it easier to work with modules, etc. The full diff is here but I'll walk through the important parts...
The first thing is that the global app has to communicate with the module via the module function. In other words, any inputs from global "space" that the module needs to use have to be passed through as reactives to the module.
In this case,
input$open_tab
was created by the global app, so we have to do a bit more work formod_iris()
(andmod_mtcars
) to see it. First I givemod_iris
anopen_tab
argument, which we can then refer to internally asopen_tab()
(because it's a reactive)and then I modify the
callModule()
to sendinput$mtcars
tomod_iris()
.As an aside, this eliminates the need for the module to extract
ns()
fromsession$ns
.This should, in general, solve the problem of the modules knowing whether or not they're visible, but there might be better ways to do this, too so keep in mind this is just one approach. (But it's also a flexible pattern that's still useful whenever modules need to communicate with the global app.)
The last thing I did was to add old-school print messages inside of each of the reactive components. The reactlog is awesome but sometimes it's a little easier to track the order of updates in the console.
Hope this helps and feel free to hit me up with any questions!