Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to return a variable from a module to the server in an R Shiny app?

Tags:

module

r

shiny

I'm having a surprising amount of difficulty returning a variable from a module to the server in an R Shiny app. In a module, I'd like to return a value when a button press is observed, so I've wrapped the return() statement in a block inside observeEvent(). However, the desired value is not returned, the entire observeEvent() block appears to be.

I've attempted to create a minimal working example outlining the problem below:

ui.R

# ui.R

fluidPage(
  input_module_ui("input"),
  actionButton("print_input_button",
               label = "Print Input")
)

server.R

# server.R

function(input, output, session) {

  # Calling input module.
  input_module_return <- callModule(input_module, "input")

  observeEvent(input$print_input_button, {
    print(input_module_return)
  })

}

global.R

# global.R

source("modules/input.R")

input.R

# input.R

input_module_ui <- function(id) {

  ns <- NS(id)

  tagList(
    textInput(ns("text_input"),
              label = h2("Input Text:")),
    actionButton(ns("submit_input"),
                 label = "Submit Input")
  )

}

input_module <- function(input, output, session) {

  print("I should only print once")

  observeEvent(input$submit_input, {
    print("Return input")
    return(input$text_input)
  })

}

When testing this app, I entered "test" in the text input box and submitted my input. However, when I attempted to print my input, instead of printing "test" as I'd expect, the following was printed:

<Observer>
  Public:
    .autoDestroy: TRUE
    .autoDestroyHandle: function () 
    clone: function (deep = FALSE) 
    .createContext: function () 
    .ctx: environment
    destroy: function () 
    .destroyed: FALSE
    .domain: session_proxy
    .execCount: 3
    .func: function () 
    initialize: function (observerFunc, label, suspended = FALSE, priority = 0, 
    .invalidateCallbacks: list
    .label: observeEvent(input$submit_input)
    .onDomainEnded: function () 
    onInvalidate: function (callback) 
    .onResume: function () 
    .prevId: 1896
    .priority: 0
    resume: function () 
    run: function () 
    self: Observer, R6
    setAutoDestroy: function (autoDestroy) 
    setPriority: function (priority = 0) 
    suspend: function () 
    .suspended: FALSE

I believe that this corresponds to the last block in input.R:

observeEvent(input$submit_input, {
    print("Return input")
    return(input$text_input)
  })

How can I get this app to work as intended and return input$text_input when input$submit_input is observed?

like image 409
michaelmccarthy404 Avatar asked Sep 12 '25 19:09

michaelmccarthy404


2 Answers

You were quite close to getting this to work. The trick with shiny modules is that passing variables into and out of them requires passing the variables as reactive values. I made two small changes to your code to get what I think you're hoping to see.

First was to return a reactive version of input$text_input from the server module (rather than from the observer itself, which should just tell the app what you want to happen):

input_module <- function(input, output, session) {

  print("I should only print once")

  observeEvent(input$submit_input, {
    print("Return input")
  })

  return(reactive({input$text_input}))

}

The second change is that now the output from input_module is reactive. If you want the values rather than the function contents, you need to resolve the object using (). So, in your server function:

server <- function(input, output, session) {

  # Calling input module.
  input_module_return <- callModule(input_module, "input")

  observeEvent(input$print_input_button, {
    print(input_module_return())
  })

}

Output:

Listening on http://127.0.0.1:6796
[1] "I should only print once"
[1] "Return input"
[1] "test"
like image 189
phalteman Avatar answered Sep 15 '25 11:09

phalteman


I found the code above to error out in my situation. Thus I thought I could provide a more generalised answer.

library(shiny)
library(tidyverse)

lower_UI <- function(id) {
  tagList(
    uiOutput(NS(id, "text3")),
    textOutput(NS(id,"verbose3")),
    actionButton(NS(id,"goButton"), label = "beep me", class = "btn-success")
  )
}

lower <- function(id) {
  moduleServer(id, function(input, output, session) {

    retV = reactiveValues(output = NULL)
    output$text3 <- renderUI(textInput(session$ns("text3"), "test", "test"))
    output$verbose3 <- renderText(input$text3)
    observe({

      retV$output = input$text3
    }) %>% bindEvent(input$goButton)

    return(reactive(retV$output))
})
}

upper_UI <- function(id) {
  lower_UI(NS(id, "test"))
}

upper <- function(id) {
  moduleServer(id, function(input, output, session) {

  uRV <- reactiveValues(one = NULL)
  output$one <- lower("test")

  observe({
    print(uRV$one())
    })
  })
}

ui <- fluidPage(
  upper_UI("upper")
)

server <- function(input, output, session) {
  upper("upper")
}

shinyApp(ui, server)
like image 24
Aaron C Avatar answered Sep 15 '25 10:09

Aaron C