Reputation: 115
Note 07/19/2023: I have updated my question. Earlier today I edited my question with the new information, but for readability purposes, I think it's reasonable to write this edit in a response. Hence, for the question's current state, see my response further down.
I am struggling with understanding how modules play together in Shiny. I believe a major part of this stems from me not understanding reactivity properly, but I've tried various different things and the best that's happened so far was that the error I got changed.
My use case is a modularized app where I try to get the following architecture to work:
What I struggle with most, I believe, is a lack of understanding what a reactive value truly is - if my backend code generates a data structure which should be updated in the main script any time there is a certain user interaction, is that saved value then a reactiveValues
? Do I need to save this as a reactiveValues
?
I have found this (Shiny Modules with Observes and reactiveValues) solution to a somewhat similar question from 2018, but from what I understand it is making use of a heavily deprecated UI which earlier I had been told to "let finally die". Anyways, here is my code. I would appreciate any kind of help.
# module.R
#-------------------------------------------------------------------------------
library(shiny)
modulePanel <- function(id) {
ns <- NS(id)
sidebarPanel(
# some user elements ...
radioButtons(
inputId = ns("lang"),
label = "Language",
choices = c("One", "Two")
)
)
}
modulePanelServer <- function(id) {
moduleServer(
id,
function(input, output, session) {
observe(
# observing some events... This should execute any time the value this is bound to changes, right?
) %>% bindEvent(ns("lang"))
reactive({
# When do I use this over observe? Another aspect that I am struggling with
}) %>% bindEvent(ns("lang"))
df <- data.frame(A=5, B=4, C="text")
# Option 1:
return(df)
# Option 2:
return(renderDataTable(df)) # but neither works
}
)
}
#-------------------------------------------------------------------------------
# main.R
#-------------------------------------------------------------------------------
source("module.R")
library(shiny)
ui <- navbarPage(
title = "some title",
modulePanel("panelId"),
mainPanel(
tabsetPanel(
tabPanel(title="Tab 1", uiOutput("module_out"))
# Target UI:
# tabPanel(title="Tab 2", uiOutput("datatableServerOutput")),
# tabPanel(title="Tab 3", uiOutput("plotServerOutput"))
)
)
)
server <- function(input, output, session) {
module_server_out <- modulePanelServer("panelId")
# Option 1:
# With this, I'm getting this error:
# Warning: Error in module_server_out: could not find function "module_server_out"
output$module_out <- module_server_out()
# Option 2:
# With this, I'm getting this error:
# Error in tempVarsPromiseDomain(outputInfoEnv, outputName = name, session = shinysession) :
# argument "name" is missing, with no default
output$module_out <- renderDataTable(module_server_out()) # ... but neither works
# Target SERVER:
# Something akin to this...
# inputServerOutput <- inputServer("inputServer")()
# output$datatableServerOutput <- datatableServer("datatableServer", inputServerOutput)()
# output$plotServerOutput <- plotServer("plotServer", inputServerOutput)()
}
shinyApp(ui, server)
Upvotes: 2
Views: 461
Reputation: 115
Note 07/19/2023: User @Limey suggested the following link to me: How to update shiny module with reactive dataframe from another module
Summarizing what I now understand, here is a rough minimal example of how I imagine my architecture to work out. My main problem is still understanding how I need to handle a module's output and when I need to use reactivity. How do I make sure that any time the radio button selection changes, my server code is actually executed again? The behaviour of observeEvent / reactiveEvent does not seem to agree with my expectations.
# main.R
#-------------------------------------------------------------------------------
library(shiny)
library(dplyr)
library(ggplot2)
source("module_panel.R")
source("module_plot.R")
ui <- navbarPage(
title = "Some title",
modulePanel("modulePanelId"),
mainPanel(
tabsetPanel(
tabPanel(title="View(DF)", tableOutput("dfView")),
modulePlot("modulePlotId")
)
)
)
server <- function(input, output, session) {
df <- reactive(modulePanelServer("modulePanelId")$df)
# Do some more stuff on output of modulePanelServer
output$dfView <- renderTable(df())
# Pass output of module 1 to module 2
modulePlotServer("modulePlotId", df)
}
shinyApp(ui, server)
# module_panel.R
#-------------------------------------------------------------------------------
modulePanel <- function(id) {
ns <- NS(id)
sidebarPanel(
radioButtons(
inputId = ns("button"),
label = "Foo",
choices = c("1", "2")
)
)
}
modulePanelServer <- function(id) {
moduleServer(id,
function(input, output, session) {
out <- reactiveValues(
df={
f <- if (input$button=="1") runif else rnorm
data.frame(
X=1:10,
Y=f(n=10)
)
}
)
return(out)
})
}
# module_plot.R
#-------------------------------------------------------------------------------
modulePlot <- function(id) {
ns <- NS(id)
uiOutput(ns("dfPlot"))
}
modulePlotServer <- function(id, df) {
moduleServer(
id,
function(input, output, session) {
output$plot <- renderPlot(
ggplot(data=df()) +
geom_point(aes(x=X, y=Y))
)
output$dfPlot <- renderUI({
ns <- session$ns
tabPanel(
title="Plot (DF)",
plotOutput(ns("plot"))
)
})
}
)
}
Upvotes: 0
Reputation: 3002
I will try to give you an answer using a modified version of your code and try to explain some of the misconceptions you have about modules, and reactivity in shiny. These concepts are not very clear and are somewhat confusing if one is not familiar with them ( the change away from callModule function and several other changes make it even harder). My explanation is based on my personal understanding on how to use shiny with modules, and when to explicit invoke reactivity and when not to.
I will start with the main app. In my understanding of the philosophy of modules, if you use them, we should try to leaverage them as much as possible, giving a nice decluttered main app.
ui <- navbarPage(
title = "Some title",
modulePanel("panelId"),
modulePanel("panelId2")
)
server <- function(input, output, session) {
modulePanelServer("panelId")
modulePanelServer("panelId2")
}
shinyApp(ui, server)
As you can see i removed basically everything from the main server function and ui object. It is only calling the same Module ui modulePanel
and its server modulePanelServer
twice with two different ids, to demonstrate the isolation between each modules data.
Module server is a R function only depending on the input id
in this case. It does not return anything in the base R sense but routes namespace dependent objects to output variables (meaning you can have several variables with the same name, containing different values across your main application).
It should do one thing in this example, look out what radiobutton (ie One
or Two
the user selected in the moduleUI (input$lang) and render a datatable with the choice in column C. If the value changes it should re-render. In this simple example, i can put everything inside the renderDataTable
function with { }
, which indicates a reactive consumer.
modulePanelServer <- function(id) {
moduleServer(id,
function(input, output, session) {
output$DT<-renderDataTable({
data.frame(A=5, B=4, C=input$lang)
})# %>% bindEvent(input$lang)
# we dont need to call bindEvent or reactive because {} invokes
# a reactive environment anyways
}
)
}
The module UI is again a function only depending on id in our simple case. It has no return value in the classical sense but returns a single html-tag (or multiple ( using tagList
) . In our example it does multiple things.
define the UI structure of everything the module implements. (all under a single html element div class="tab-pane" title=" "
you can even try it out by calling modulePanel("Hallo") outside of shiny, in the R console and check out the HTML it generates.)
further provide namespacing by wrapping variable names in the NS()
function
then it creates an input element (radiobutton in the sidebar panel)
and lastly it renders the dataTableOutput provided from its modulePanelServer
counterpart with the same id.
modulePanel <- function(id) {
# You want to render all input that is created by the modules server function
# and put it in a tag (list) so i can be accessed by the global UI function
tabPanel(
title = paste("Tab: ", id),
# input radio buttons
sidebarLayout(
sidebarPanel(
radioButtons(
inputId = NS(id,"lang"),
label = "Language",
choices = c("One", "Two")
)
),
# output
mainPanel(
title="Tab 1",
dataTableOutput(NS(id,"DT"))
)
)
)
}
Of course this is just a small example and your real world application is probably more complex, and may require to use explicit reactivity (ie reactive
function. ) and some styling in the global UI function, but i didn't include those aspects to give a general/minimal "best practice" example to showcase the basis of modules for shiny.
Upvotes: 1
Reputation: 21297
You can return a reactive object and display via renderDataTable()
or renderUI()
.
Try this
# module.R
library(shiny)
modulePanel <- function(id) {
ns <- NS(id)
sidebarPanel(
# some user elements ...
radioButtons(
inputId = ns("lang"),
label = "Language",
choices = c("One", "Two")
)
)
}
modulePanelServer <- function(id) {
moduleServer(
id,
function(input, output, session) {
# observe(
# # observing some events... This should execute any time the value this is bound to changes, right?
# ) %>% bindEvent(input$lang)
#
# reactive({
# # When do I use this over observe? Another aspect that I am struggling with
# }) %>% bindEvent(ns("lang"))
df <- reactive({data.frame(A=c(1:5), B=c(4:8), C=c(rep(input$lang,5) ))})
return(df) ### return reactive object
# Option 2:
# return(renderDataTable(df)) # but neither works
}
)
}
#-------------------------------------------------------------------------------
# main.R
#-------------------------------------------------------------------------------
# source("module.R")
ui <- navbarPage(
title = "some title",
modulePanel("panelId"),
mainPanel(
tabsetPanel(
tabPanel(title="Tab 1", uiOutput("module_out")),
tabPanel(title="Tab 2", dataTableOutput("module_out2") )
)
)
)
server <- function(input, output, session) {
mydf <- modulePanelServer("panelId")
# Option 1:
output$module_out <- renderUI({print(mydf()[1,])})
# Option 2:
output$module_out2 <- renderDataTable(mydf())
}
shinyApp(ui, server)
Upvotes: 1