Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save sharlagelfand/20646a4c02950c2a0a6d351b70d00a96 to your computer and use it in GitHub Desktop.
Save sharlagelfand/20646a4c02950c2a0a6d351b70d00a96 to your computer and use it in GitHub Desktop.
shiny modules + reactlog + not independent
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)
@gadenbuie
Copy link

👋 @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 for mod_iris() (and mod_mtcars) to see it. First I give mod_iris an open_tab argument, which we can then refer to internally as open_tab() (because it's a reactive)

mod_iris <- function(input, output, session, open_tab = reactive("")) {
  df <- reactive({
    req(open_tab() == "mtcars")

    df <- mtcars
  })
}

and then I modify the callModule() to send input$mtcars to mod_iris().

callModule(
  mod_iris,
  "iris",
  open_tab = reactive(input$open_tab)
)

As an aside, this eliminates the need for the module to extract ns() from session$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!

@gadenbuie
Copy link

gadenbuie commented Jul 12, 2019

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.

@chasemc
Copy link

chasemc commented Jul 12, 2019

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.

@sharlagelfand
Copy link
Author

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!!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment