R Shiny: How to Save Returned Value from a Module?
I somehow solved it by doing this:
Module:
custSliderGroupInput <- function(id,slider1Name,slider2Name){
ns <- NS(id)
tagList(sliderInput(ns("slider1"),slider1Name,1,100,50),
sliderInput(ns("slider2"),slider2Name,1,20,10))
}
custSliderGroup <- function(input,output,session){
rv <- input$slider1 + input$slider2
return(rv)
}
App
ui <- fluidPage(
custSliderGroupInput("myslider","A","B"),
textOutput("text")
)
server <- function(input, output,session){
output$text <- renderText({
callModule(custSliderGroup,"myslider")
})
}
shinyApp(ui = ui, server = server)
I don't know why, but it seems like using functionalities such as reactive()
or observeEvent()
makes the module environment too complicated and does more harm than good. It just works by simplifying the codes. If anyone knows how this theoretically works or doesn't work please post your answer!
Thanks a lot!
How to return a reactive dataframe from within a shiny module that depends on a button click?
Try this
library(shiny)
library(dplyr)
df_agg_orig <- data.frame(proj_1 = c(2,3))
modGrowthInput <- function(id) {
ns <- NS(id)
tagList(
numericInput(ns("first"),label = "Assumption",value = 10),
)
}
modGrowthServer <- function(id) {
moduleServer(id, function(input, output, session) {
list(
first = reactive({input$first})
)
})
}
modButtonUI <- function(id,lbl = "Recalculate"){
ns <- NS(id)
actionButton(inputId = ns("btn"),label = lbl)#,style = "pill",color = "primary",no_outline = T,size = "xs"
}
modButtonServer <- function(id){
moduleServer(id, function(input, output, session) {
reactive({input$btn})
})
}
modApplyAssumpServer <- function(id,btnGrowth, df_agg, val){
moduleServer(id, function(input, output, session) {
stopifnot(is.reactive(btnGrowth))
stopifnot(is.reactive(df_agg))
modvals <- eventReactive(btnGrowth(), {
print("Looping problem...")
#print(btnGrowth())
df_agg() %>% mutate(proj_1 = proj_1*val )
})
return(modvals())
})
}
#### Test App
GrowthInputApp <- function() {
ui <- fluidPage(
sidebarPanel(modGrowthInput("tst"),modButtonUI("tstGrowth")),
mainPanel(fluidRow( splitLayout( DT::DTOutput("no_module"),DT::DTOutput("module_tbl")))))
server <- function(input, output, session) {
btnGrowth <- modButtonServer("tstGrowth")
case_vals <- modGrowthServer("tst")
observe({ print(case_vals$first())})
df_agg <- reactiveValues(df_wide = df_agg_orig)
#Outside of module test exhibiting expected/desired behavior (at least if the looping issue would let it do so :)
observeEvent(btnGrowth(),{
df_agg$df_wide$proj_1 <- round(df_agg$df_wide*case_vals$first(),2)
})
mydf <- eventReactive(c(btnGrowth(),case_vals$first()), {
modApplyAssumpServer("tst", btnGrowth, reactive({df_agg$df_wide}), case_vals$first() )
})
#observe({print(btnGrowth())})
output$no_module <- renderDT({DT::datatable(rownames = F,df_agg$df_wide,caption = "Not Updated Within Module")})
output$module_tbl <- renderDT({DT::datatable(rownames = F, mydf() ,caption = "Table Returned From Module")} )
### using original data so no change after first click
#output$module_tbl <- renderDT({DT::datatable(rownames = F, modApplyAssumpServer("tst", btnGrowth, reactive({df_agg_orig}), case_vals$first() ),caption = "Table Returned From Module")}
#)
}
shinyApp(ui, server)
}
runApp(GrowthInputApp())
Access the returned values of dynamically created Shiny modules
Here is a solution that uses insertUI
. It has the advantage that existing UI elements stay the same (no resetting of the previous modules) and only new modules are added. To determine where the UI is added, define a tags$div(id = "tag_that_determines_the_position")
in the UI
function. Then, insertUI
takes this as an argument. Additionally, I've changed a few things:
- simplified the code for the module server function, you basically only need a
reactive
- use of the new module interface introduced with shiny 1.5.0
- use a bit simpler reactive data structure (less nesting)
library(shiny)
library(stringr)
gen_r_8_formUI <- function(id){
ns <- NS(id)
tagList(fluidRow(column(width = 4, selectInput(ns("slt_forename"), 'forename', choices = unique(c("john", "paul", "george", "ringo")))),
column(width = 4, selectInput(ns("slt_surname") , 'surname' , choices = unique(c("lennon", "mccartney", "harrison", "starr"))))))
}
gen_r_8_form <- function(id){
moduleServer(
id,
function(input, output, session) {
select_values_all <- reactive({tibble(forename = input$slt_forename,
surname = input$slt_surname)})
return(list(select_values_all = reactive({select_values_all()})))
}
)
}
ui <- fluidPage(
column(width = 2, actionButton("btn_gen_r_8_form", "GEN R 8 a FORM")),
column(width = 6, tags$div(id = "add_UI_here")),
column(width = 4, tableOutput("all_form_values_table")))
server <- function(input, output) {
gen_forms <- reactiveValues()
current_id <- 1
observeEvent(input$btn_gen_r_8_form, {
x_id <- paste0("module_", current_id)
gen_forms[[x_id]] <- gen_r_8_form(id = x_id)
insertUI(selector = "#add_UI_here",
ui = gen_r_8_formUI(x_id))
current_id <<- current_id + 1
})
all_form_values_rctv <- reactive({
res <- lapply(reactiveValuesToList(gen_forms), function(current_module_output) {
current_module_output$select_values_all()
})
# prevent to show an error message when the first module is added
if (length(res) != 0 && !is.null(res[[1]]$forename)) {
dplyr::bind_rows(res)
} else {
NULL
}
})
output$all_form_values_table <- renderTable({
all_form_values_rctv()
})
}
shinyApp(ui = ui, server = server)
How to access reactive value in parent module?
You can access with value()
. I would recommend to change your mod_server_btn
to the one shown below, and notice the call in server
. EDIT: updated for multiple variables. Try this
library(shiny)
mod_ui_btn <- function(id, label = "ui1UI") {
ns <- NS(id)
shinyUI(fluidPage(
actionButton(ns("confirm"), "Submit", class='btn-primary'),
actionButton(ns("confirm2"), "Submit2", class='btn-primary')
))
}
mod_server_btn <- function(id) {
moduleServer(id, function(input, output, session) {
return(
list(
cond = reactive(input$confirm),
cond2 = reactive(input$confirm2)
)
)
})
}
ui =fluidPage(
mod_ui_btn("test"),
verbatimTextOutput("example"),
verbatimTextOutput("example2")
)
server=shinyServer(function(input, output, session) {
# value <- callModule(mod_server_btn,"test")
value <- mod_server_btn("test")
output$example <- renderPrint(value$cond())
output$example2 <- renderPrint(value$cond2())
observe({
print(value$cond()) #this is how I usually catch reactives - by their name
print(value$cond2())
})
})
shinyApp(ui=ui,server=server)
shiny module inside module loosing reactive value
In your filterServer
function you have to use session$ns("var")
instead of NS(id, "var")
. The former will include enclosing namespace whereas the later will only include current namespace. I added two messages that will show in the console what I mean.
filterServer <- function(id, df) {
moduleServer(id, function(input, output, session) {
message("session namespace: ", session$ns("test"))
message("raw namespace: ", NS(id, "test"))
vars <- reactive(names(df))
output$controls <- renderUI({
map(vars(), function(var) make_ui(df[[var]], session$ns(var), var))
})
reactive({
each_var <- map(vars(), function(var) filter_var(df[[var]], input[[var]]))
reduce(each_var, `&`)
})
})
}
Rbind data returned by a shiny module to a reactiveValues object
tl;dr
Replace return(values2$new_row)
by return(reactive({values2$new_row}))
in your module
function and x
by x()
in the observeEvent
inside your server
side.
You can a working example here :
library(shiny)
options(shiny.error=recover)
data <- data.frame(x = 1:10)
moduleUI <- function(id) {
ns <- NS(id)
actionButton(ns("append"), "Append row")
}
module <- function(input, output, session) {
values2 <- reactiveValues(new_row = NULL)
observeEvent(input$append, {
values2$new_row <- data.frame(x = sample(1:100, 1))
})
return(reactive({values2$new_row}))
}
ui <- fluidPage(
moduleUI("mod"),
tableOutput("table")
)
server <- function(input, output, session) {
values <- reactiveValues(data = data)
x <- callModule(module, "mod")
observeEvent(x(), {
values$data <- rbind(values$data, x())
})
output$table <- renderTable({
values$data
})
}
shinyApp(ui, server)
If you want to understand how I got to the above, see the following:
When running your code, we get the following message:
Warning: Error in .getReactiveEnvironment()$currentContext: Operation not allowed without an active reactive context. (You tried to do something that can only be done from inside a reactive expression or observer.)
61: stop
60: .getReactiveEnvironment()$currentContext
59: .subset2(x, "impl")$get
58: $.reactivevalues
56: module [#10]
51: callModule
50: server [#5]
Error in .getReactiveEnvironment()$currentContext() :
Operation not allowed without an active reactive context. (You tried to do something that can only be done from inside a reactive expression or observer.)
This says that we are having problems in line 5 of the server
function (the line with x <- callModule(module, "mod")
) and line 10 of the module
function (the line with return(values2$new_row)
).
Putting browser()
in line 9 of the module
function and running the code again makes the browser stops right before return(values2$new_row)
. Running this piece of code in browser yields the error message:
Error in .getReactiveEnvironment()$currentContext() :
Operation not allowed without an active reactive context. (You tried to do something that can only be done from inside a reactive expression or observer.)
This means that whatever you are returning should be inside a reactive or part of an observe function. So, we write return(reactive({values2$new_row}))
and hit enter to see that this works (i.e. it doesn't give any error and our debugger goes to line 10 of the module
function).
Typing Q and hitting enter to leave browser mode and get back to our code. Put the correct code return(reactive({values2$new_row}))
.
But that's not all. Because our function is now returning a reactive value, we have to call x
using x()
. So, changing this in the observeEvent
bit yields:
observeEvent(x(), {
values$data <- rbind(values$data, x())
})
How does one retrieve values from a shiny module that are created iteratively based on user input?
Here is a very hacky way to do it. If someone comes up with a better solution I'd be very happy! You basically store the unevaluated reactives returned by the module in a reactiveValues
object and then evaluate every element of reactiveValues
in a new reactive
. You could probably also use an ordinary list instead of reactiveValues
, but then you would need to use <<-
to assign the values inside the observeEvent
which can lead to problems.
server <- function(input, output, session) {
mod_results <- reactiveValues()
vec <- reactive({
switch(input$items, Animals = Animals, Vehicles = Vehicles)
})
output$levels_fills <- renderUI(
purrr::map2(vec(), seq_along(vec()), function(level, n) {
mod_fill_def_ui(glue("fill_{level}"), level, length(vec()), n)
})
)
### How do I use `mod_fill_def_server()` iteratively to retrieve the hex codes?
observeEvent(vec(), {
purrr::map(vec(), function(level) {
mod_results[[glue("fill_{level}")]] <- mod_fill_def_server(id = glue("fill_{level}"))
})
})
hex <- reactive({
lapply(reactiveValuesToList(mod_results), function(current_module_output) {
current_module_output()
})
})
output$fill_hex <- renderPrint({
hex()
})
}
However, if your module only contains the colourInput
, you don't need a module but could use the colourInput
directly:
server <- function(input, output, session) {
vec <- reactive({
switch(input$items, Animals = Animals, Vehicles = Vehicles)
})
output$levels_fills <- renderUI(
purrr::map2(vec(), seq_along(vec()), function(level, n) {
colourInput(inputId = glue("fill_{level}"),
label = glue("Colour for {level}"),
value = hue_pal()(length(vec()))[n])
})
)
hex <- reactive({
purrr::map(vec(), function(level) {
input[[glue("fill_{level}")]]
})
})
output$fill_hex <- renderPrint({
hex()
})
}
R Shiny: How can I return reactive values from a shiny module to the master server function?
As said in the comment, you can pass the values as return values in the corresponding server functions. There is a working example below. I left out the firstUI
, firstServer
and removeFirstUI
implementations since they are irrelevant for your problem.
library(shiny)
addRmBtnUI <- function(id) {
ns <- NS(id)
tags$div(
actionButton(inputId = ns('insertParamBtn'), label = "Add"),
actionButton(ns('removeParamBtn'), label = "Remove"),
hr(),
tags$div(id = ns('placeholder'))
)
}
addRmBtnServer <- function(input, output, session, moduleToReplicate, ...) {
ns = session$ns
params <- reactiveValues(btn = 0)
observeEvent(input$insertParamBtn, {
params$btn <- params$btn + 1
callModule(moduleToReplicate$server, id = params$btn, ...)
insertUI(
selector = paste0('#', ns('placeholder')),
ui = moduleToReplicate$ui(ns(params$btn))
)
})
observeEvent(input$removeParamBtn, {
moduleToReplicate$remover(ns(params$btn))
params$btn <- params$btn - 1
})
return(reactive({params$btn}))
}
ui <- fluidPage(
addRmBtnUI("addRm"),
verbatimTextOutput("view", placeholder = TRUE)
)
server <- function(input, output, session) {
a <- reactive({input$a})
pars <- callModule(
addRmBtnServer, id = "addRm",
moduleToReplicate = list(
ui = function(...){},
server = function(...){},
remover = function(...){}
)
)
output$view <- renderText({ pars() })
}
shinyApp(ui = ui, server = server)
Related Topics
How to Replace Numeric Codes with Value Labels from a Lookup Table
Access Data Frame Column Using Variable
How to Loop Through a Folder of CSV Files in R
Delete Rows Based on Multiple Conditions with Dplyr
How to Ddply() Without Sorting
R How to Extract First Row of Each Matrix Within a List
Names' Attribute Must Be the Same Length as the Vector
Plotting Functions on Top of Datapoints in R
How to Pass Data Between Functions in a Shiny App
How to Print the Structure of an R Object to the Console
Ggplot2 Multiline Title, Different Indentations
Specifying the Scale for the Density in Ggplot2's Stat_Density2D
How to Turn the Numeric Output of Boxplot (With Plot=False) into Something Usable
Split a Vector into Three Vectors of Unequal Length in R
Plotting Continuous and Discrete Series in Ggplot with Facet