6
votes

I am using renderUI to optionally present a Table or Plot based on user selection of the visualization option. I am also using Shiny modules to present the same thing on multiple tabs. While I have gotten Shiny modules to work wonderfully in another app, I am struggling to get it to work with renderUI.

Here is a minimal piece of code that I came up with that shows the problem where nothing gets displayed on either tabs:

myUI <- function(id) {
  ns <- NS(id)
  fluidRow(
    uiOutput(ns('myFinalText'))
  )
}

ui <- fluidPage(
  tabBox(id = 'myBox', width = 12,
         tabPanel('Tab1',
                  fluidRow(
                    myUI('tab1')
                  )),
         tabPanel('Tab2',
                  fluidRow(
                    myUI('tab2')
                  ))
         )
)

myTextFunc <- function(input, output, session, text) {
  output$myFinalText <- renderUI({
    output$myText <- renderText({text})
    textOutput('myText')
  })
}

server <- function(input, output, session) {
  callModule(myTextFunc, 'tab1', session = session, 'Hello Tab1')
  callModule(myTextFunc, 'tab2', session = session, 'Hello Tab2')
}

shinyApp(ui = ui, server = server)

Any thoughts on what else I should be doing to make this work?

Replacing the Shiny module UI function and server functions as follows makes it work fine.

myUI <- function(id) {
  ns <- NS(id)
  fluidRow(
    textOutput(ns('myFinalText'))
  )
}

myTextFunc <- function(input, output, session, text) {
  output$myFinalText <- renderText({
    text
  })
}
4
Having textOutput in the server function doesn't really make sense. As you say, the replacement works, so what is the first version supposed to be doing that isn't provided by the second? I am confused by the intent of your myTextFunc nested structure. - Brian Stamper
Obviously my code (UI and server functions are very complex) and doing much more than rendering simple text. I am just using this example to show that renderUI is not working with Shiny Modules - in my case as I am trying to use it. I want the renderUI to render the same 'tab1' and 'tab2' text. If this works, I can figure out my real use case. - Gopala

4 Answers

5
votes

You can get the namespace from the session object. Change myTextFunc in the initial app like this:

myTextFunc <- function(input, output, session, text) {
    ns <- session$ns
    output$myFinalText <- renderUI({
        output$myText <- renderText({text})
        textOutput(ns('myText'))
    })
}
3
votes

You shouldn't call output$ function from another output$ function - it's against Shiny design patterns.

output$myFinalText <- renderUI({
    output$myText <- renderText({text})
    textOutput(ns('myText'))
})

If you want to know, why it is very bad practice, watch Joe Cheng tutorial about 'Effective reactive programming' from this site: https://www.rstudio.com/resources/webinars/shiny-developer-conference/.

You should use rather reactiveValues or reactive expressions instead. What exactly you should use is dependent from what do you want to achieve, so it's hard to say without detailed example, but according to Joe Cheng everything can be accomplished without nesting outputs or observers.

1
votes

Sorry for answering my own question...but for others looking for a similar solution, this may be of help.

Here is how I solved for the need to inherit Shiny module namespace on the server side to dynamically render UI. IF there is a better way to solve, please comment or post.

tab1NS <- NS('tab1')
tab2NS <- NS('tab2')

myUI <- function(ns) {
  tagList(
    fluidRow(
      radioButtons(ns('type'), 'Select Visual:',
                   choices = c('Table' = 'table',
                               'Plot' = 'plot'))
    ),
    fluidRow(
      uiOutput(ns('myCars'))
    )
  )
}

ui <- fluidPage(
  tabBox(id = 'myBox', width = 12,
         tabPanel('Tab1',
                  fluidRow(
                    myUI(tab1NS)
                  )),
         tabPanel('Tab2',
                  fluidRow(
                    myUI(tab2NS)
                  ))
         )
)

myTextFunc <- function(input, output, session, cars, ns) {
  getMyCars <- reactive({
    if (input$type == 'table') {
      output$table <- renderDataTable({datatable(cars)})
      dataTableOutput(ns('table'))
    } else{
      output$plot <- renderPlot({
        plot(cars$wt, cars$mpg)
      })
      plotOutput(ns('plot'))
    }
  })
  output$myCars <- renderUI({
    getMyCars()
  })
}

server <- function(input, output, session) {
  callModule(myTextFunc, 'tab1', session = session,
             mtcars[mtcars$am == 1, ], tab1NS)
  callModule(myTextFunc, 'tab2', session = session,
             mtcars[mtcars$am == 0, ], tab2NS)
}

shinyApp(ui = ui, server = server)
1
votes

Replacing your functions with this renderUI equivalent also works:

myUI <- function(id) {
  ns <- NS(id)
  fluidRow(
    uiOutput(ns('myFinalText'))
  )
}

myTextFunc <- function(input, output, session, text) {
  output$myFinalText <- renderUI({
    text
  })
}

Although this obviously does not capture the complexity of what you are really doing. There's something not right about using output$... and textOutput within the renderUI like that. I don't think that is necessary - you don't actually have to use the textOutput function to include text in your output.

EDIT: It occurs to me that the problem has to do with namespaces and modules. When you do output$myText <- renderText(text), the result ends up in the namespace of tab1 or tab2. For example, try changing your textOutput to

textOutput('tab1-myText')

and watch what happens. I think this is why having output$.. variables in your renderUI is problematic. You can access inputs via callModule and that should take care of any namespace issues.