2

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:

  • main script contains ui object and server function
    • the ui object utilizes a modularized sidebarPanel containing various interactive elements
    • the server maintains the selected language (best done via session$userData?) and calls three modules, these being:
  1. server function belonging to the modularized sidebarPanel:
    • prepares input data and calls backend algorithm. Returns a list of dataframes to the main script for further processing from the main script
  2. makeDatatable function turning the list of data frames into some nicely formatted datatable. Before modularizing, this part of the code set output$datatableUI by calling renderUI on a tabsetPanel.
  3. makePlots function generating a tabsetPanel of plotOutput objects. This, too, previously set output$plotUI by calling renderUI on a tabsetPanel

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)

  • 1
    Does this answer your question? [How to update shiny module with reactive dataframe from another module](https://stackoverflow.com/questions/68584478/how-to-update-shiny-module-with-reactive-dataframe-from-another-module) – Limey Jul 19 '23 at 09:13
  • This was actually extremely helpful, and I now understand how to pass objects such as dataframes out of modules to continue working with them outside their original module. However, there is one more thing (see my answer below for more detail): How do I pass the output of the renderFoo and fooOutput functions on? I cannot get that idea to work. – J. Grünenwald Jul 19 '23 at 12:18
  • I don't think you should attempt to "pass on the output" of `renderFoo` and `fooOutput` functions. These functions render and create widgets. Each call acts on a single widget. To me, it makes no sense to pass them between other program elements. What you may want to do - and this is more sensible, IMHO - is to render or adapt different widgets as information changes. This information would be, say, a `reactive`. Are you perhaps confusing the underlying internal object with its external representation? They are two completely separate, though linked, things. – Limey Jul 19 '23 at 12:23
  • I understand it may not be best practice to pass these objects on (why?), but practically, what's stopping me from doing just that? As @user12256545 explained, something like the output of ```modulePanel``` is just an HTML tag, so whether I output it in a ui function belonging to the module, or whether I pass it out and display it from somewhere else - does that really make a difference? – J. Grünenwald Jul 19 '23 at 12:28

3 Answers3

1

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)
YBS
  • 19,324
  • 2
  • 9
  • 27
  • Thank you for your response. See @user12256545's response, I have elaborated further on my question in my original post. Do you think that maybe you could help me out there? – J. Grünenwald Jul 19 '23 at 07:28
1

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.

Main app:

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

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
    }
  )
}

Module UI:

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.

user12256545
  • 2,755
  • 4
  • 14
  • 28
  • Thank you. This helps me a bit, but my specific issue is still not quite resolved. I am updating my question and would be grateful if you could give me some more in-depth advice on how my modules can be connected together since, as you guessed, my app is more complex than this. – J. Grünenwald Jul 19 '23 at 07:06
0

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"))
        )
      })
    }
  )
}
  • i think, since modulePanel is not rendering output it should not be a module but a function, further modulePlotServer should render output in its own UI function and not use renderUI/uiOutput unless it absolutley has to provide server side rendering. – user12256545 Jul 19 '23 at 12:22
  • Actually, modulePanel also depends on its own input and some further calculations on that to dynamically change its appearance. So if I understand you correctly then I think this architecture is appropriate? – J. Grünenwald Jul 19 '23 at 12:35
  • In that case it makes sense, but why cant the renderDataTable also be done inside the module? – user12256545 Jul 19 '23 at 12:37
  • It can absolutely be done inside the module, I think I was just translating my code pre-modularization in a fairly braindead way. – J. Grünenwald Jul 19 '23 at 12:39
  • ok so the only issue then is, that the df from modulePanel should be accessed in the Plot module. – user12256545 Jul 19 '23 at 12:44
  • That in itself is not an issue. I've learnt how to pass and receive elements from modules and how apparently they all have to be ```reactive``` (which I guess makes sense). But my very last use case - generating a ```plot``` in a different module and displaying it - fails. Could you maybe take a look at my updated code in in this answer and see if something catches your eye? I am talking about the code in ```module_plot.R```. Thank you for your help! – J. Grünenwald Jul 19 '23 at 13:07
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/254565/discussion-between-j-grunenwald-and-user12256545). – J. Grünenwald Jul 19 '23 at 13:08
  • One thing of interest I have noticed is this: By changing two lines of code, I can make this work. Instead of ```uiOutput(ns("dfPlot"))```, I call ```tabPanel(title="Plot (DF)", plotOutput(ns("plot"))``` in the ui. Thus I no longer have a need to call ```renderUI(...)``` inside my server and can just use the rendered plot. Obviously, this only works in this very limited scenario. I am assuming that I am facing some sort of namespace issue here? – J. Grünenwald Jul 19 '23 at 13:15