How can I send and update reactive values from within a R Shiny module? - shiny

I'm trying to write a module that gets a reactive dataframe as an input and allows the user to manipulate it (for the sake of the Minimal Reproducible Example, to add to the table a single row)
Initially, the data that being passed to the module from the main app is some default dataframe (hard coded in the MRE), so the module is always initiated with data.
In addition, I also want to allow the user to manipulate that data from outside the module (for the sake of the MRE, override the dataset with a different, hard-coded dataset).
I cannot make both functionalities in the MRE to work at the same time. At the moment, the update from main app works, but the update from within the module won't work. I found some solutions that would enable the opposite situation.
when trying to add row: no response and no error.
Note 1: The use of modules in the MRE is artificial and not really needed, but it is very much needed in the real app.
Note2: returning a new data frame instead of updating it is not ideal in my case as I would want to allow the user other manipulations, and only after all changes are done, to return the the new data frame.
Minimal Reproducible Example:
library(shiny)
library(tidyverse)
DEFAULT_DATA <- tribble(
~letter, ~number,
"A", 1,
"B", 2,
)
changeDataUI <- function(id) {
ns <- NS(id)
tagList(
tableOutput(ns("tbl"))
,br()
,actionButton(ns("add_row"), 'Add Row')
)
}
changeDataServer <- function(id, data) {
moduleServer(
id,
function(input, output, session) {
observeEvent(input$add_row, {
data <- data() %>% add_row(letter = "C", number = 3)
})
output$tbl <- renderTable(data())
}
)
}
ui <- fluidPage(
titlePanel("MRE App")
,fluidRow(column(6, actionButton("change_dataset", "Change Dataset")))
,fluidRow(column(6, changeDataUI("some_id")))
)
server <- function(input, output) {
glob_rvs <- reactiveValues(data = DEFAULT_DATA)
observeEvent(input$change_dataset, {
glob_rvs$data <- tribble(
~letter, ~number,
"D", 4,
"E", 5,
)
})
changeDataServer(id = "some_id", data = reactive(glob_rvs$data))
}
shinyApp(ui = ui, server = server)

With R, you typically want your modules to act as functional as possible. This, as you point out allows you to better reason about your app. I would have your module return the rows to be added and then have your top level app append them. Otherwise you module is essentially causing side effects. Also, this way your top level app (or another module) could coordinate multiple manipulations. The module could still show the data
Example implementation for module server:
changeDataServer <- function(id, data) {
moduleServer(
id,
function(input, output, session) {
additionalRows <- reactiveVal()
observeEvent(input$change, {
additionalRows(
data.frame(letter = sample(letters, 1) , number = runif(1, 0, 10))
)
})
output$tbl <- renderTable(data())
# return reactive with additional rows to allow to be merged at top level
additionalRows
}
)
}
Then update the server (also changed the input for the upload handler to match the UI (input$uploaded_data not input$uploaded_scheme)
server <- function(input, output) {
glob_rvs <- reactiveValues(data = DEFAULT_DATA)
observeEvent(input$uploaded_data, {
uploaded_data <- read_csv(input$uploaded_data$datapath)
glob_rvs$data <- uploaded_data
})
newRows <- changeDataServer(id = "some_id", data = reactive(glob_rvs$data))
observe({
glob_rvs$data <- bind_rows(glob_rvs$data, newRows())
}) %>%
bindEvent(newRows())
}

What you want to do here is to pass you reactiveValues object as an argument of your module server.
I advise you read this article about how to communicate data between modules
library(shiny)
library(tidyverse)
DEFAULT_DATA <- tribble(
~letter, ~number,
"A", 1,
"B", 2,
)
changeDataUI <- function(id) {
ns <- NS(id)
tagList(
tableOutput(ns("tbl"))
,br()
,actionButton(ns("change"), 'Add Row')
)
}
changeDataServer <- function(id, glob_rvs) {
moduleServer(
id,
function(input, output, session) {
observeEvent(input$change, {
print(glob_rvs$data)
glob_rvs$data <- glob_rvs$data %>% add_row(letter = "C", number = 3)
})
output$tbl <- renderTable(glob_rvs$data)
}
)
}
ui <- fluidPage(
titlePanel("MRE App")
,fluidRow(column(6,
fileInput("uploaded_data",
"would ypu like to upload your own data?",
multiple = FALSE,
accept = c(".csv"))))
,fluidRow(column(6, changeDataUI("some_id")))
)
server <- function(input, output) {
glob_rvs <- reactiveValues(data = DEFAULT_DATA)
observeEvent(input$uploaded_data, {
uploaded_data <- read_csv(input$uploaded_scheme$datapath)
glob_rvs$data <- uploaded_data
})
changeDataServer(id = "some_id", glob_rvs = glob_rvs)
}
shinyApp(ui = ui, server = server)

Following the solution by #Marcus, here is a working version that is consistent with the last version of post:
library(shiny)
library(tidyverse)
DEFAULT_DATA <- tribble(
~letter, ~number,
"A", 1,
"B", 2,
)
changeDataUI <- function(id) {
ns <- NS(id)
tagList(
tableOutput(ns("tbl"))
,br()
,actionButton(ns("add_row"), 'Add Row')
)
}
changeDataServer <- function(id, data) {
moduleServer(
id,
function(input, output, session) {
additionalRows <- reactiveVal()
observeEvent(input$add_row, {
additionalRows(
data.frame(letter = sample(letters, 1) , number = runif(1, 0, 10))
)
})
output$tbl <- renderTable(data())
# return reactive with additional rows to allow to be merged at top level
additionalRows
}
)
}
ui <- fluidPage(
titlePanel("MRE App")
,fluidRow(column(6, actionButton("change_dataset", "Change Dataset")))
,fluidRow(column(6, changeDataUI("some_id")))
)
server <- function(input, output) {
glob_rvs <- reactiveValues(data = DEFAULT_DATA)
observeEvent(input$change_dataset, {
glob_rvs$data <- tribble(
~letter, ~number,
"D", 4,
"E", 5,
)
})
newRows <- changeDataServer(id = "some_id", data = reactive(glob_rvs$data))
observe({
glob_rvs$data <- bind_rows(glob_rvs$data, newRows())
}) %>%
bindEvent(newRows())
}
shinyApp(ui = ui, server = server)

Related

Call for input inside moduleServer

I'm learning Shiny modules. And I'm stuck in a very silly thing: I don't know how to call an input inside moduleServer. In this reprex, the table does not show, I think its because the getInput argument is not properly used in the server. Here's a reprex:
library(shiny)
library(DT)
tablaResumen <- function(id, getInput, tabla1, tabla2) {
moduleServer(id, function(input, output, session) {
output$table <- renderDT({
if(getInput == FALSE){
tabla <- tabla1
}else{
tabla <- tabla2
}
DT::datatable(tabla, escape = FALSE, rownames = FALSE)
})
})
}
ui <- fluidPage(
checkboxInput("input1", label = "Change table"),
DTOutput("table1")
)
server <- function(input, output, session) {
tablaResumen("table1", input$input1, mtcars, iris)
}
shinyApp(ui, server)
library(shiny)
library(DT)
tablaResumen <- function(id, parent_in, get, tabla1, tabla2) {
moduleServer(id, function(input, output, session) {
output$mytable <- renderDT({
if(parent_in[[get]] == FALSE){
tabla <- tabla1
}else{
tabla <- tabla2
}
DT::datatable(tabla, escape = FALSE, rownames = FALSE)
})
})
}
tablaResumenUI <- function(id) {
ns <- NS(id)
DTOutput(ns("mytable"))
}
ui <- fluidPage(
checkboxInput("input1", label = "Change table"),
tablaResumenUI("table")
)
server <- function(input, output, session) {
tablaResumen("table", parent_in = input, "input1", mtcars, iris)
}
shinyApp(ui, server)
Things are a little tricky here.
To render the table, you must put the DTOutput under the same namespace as your mod server. The way we usually do it is by creating a mod UI function and use NS to wrap the id to create the namespace.
You module is depend on a reactive input value input$input1, but the server function itself is not reactive. This means if you provide it as an argument for the mod function, it will be run only one time, so getInput will never be changed after the app is initialized. It becomes a fixed value. To get the reactive value of input1, you need to provide the parent input as an argument as access from there.

rShiny Looping on ui filter conditions

I am trying to create a dashboard in rShiny which follow the following steps
Select a parameter
Filter data from a source table for this parameter
Create a list of this filtered data for one of the column
Iterate over this list to display graphs etc...
I have tried various options for making this work but the communication between ui and server is not happening as expected
I have created a setup as below fot testing
library(shiny)
df_mtcars <- mtcars
df_mtcars <- cbind(CarName = rownames(df_mtcars), df_mtcars)
df_mtcars$CarName <- sub(" ", "_", df_mtcars$CarName)
select the number of gears
Find the cars with that number of gears
Create a list of these cars
Display the data for each of the car by using loop. Loop is needed as other output types like graphs can be latter added
simpUI <- function(id) {
tagList(tableOutput(NS(id, "dat_output"))
numericInput(NS(id, "GearNumber"), "Gear Numbers", 3),
lapply(seq(1, length(v_lst_CarName), by = 1), function(i) {
v_CarName = v_lst_CarName[i]
v_obj_CarName = paste0('sp_cars_', v_CarName)
tableOutput(NS(id, v_obj_CarName))
}))
}
simpServer <- function(id) {
moduleServer(id, function(input, output, session) {
output$dat_output <- renderTable(df_mtcars)
v_lst_CarName <-
reactive(df_mtcars[GearNumber == input$GearNumber]$CarName)
for (v_CarName in v_lst_CarName)
v_obj_CarName = paste0('sp_cars_', v_CarName)
output$v_obj_CarName <- renderTable(v_obj_CarName)
})
}
ui <- fluidPage(fluidRow(simpUI("cars")))
server <- function(input, output, session) {
simpServer("cars")
}
shinyApp(ui = ui, server = server)
It is better to do server side processing. Try this
library(shiny)
library(ggplot2)
df_mtcars <- mtcars
df_mtcars <- cbind(CarName = rownames(df_mtcars), df_mtcars)
df_mtcars$CarName <- sub(" ", "_", df_mtcars$CarName)
simpUI <- function(id) {
ns <- NS(id)
tagList(tableOutput(ns("dat_output")),
numericInput(ns("GearNumber"), "Gear Numbers", 3),
uiOutput(ns("plotxy")),
tableOutput(ns("v_obj_CarName")),
verbatimTextOutput(ns("mylist")),
plotOutput(ns("myplot"))
)
}
simpServer <- function(id) {
moduleServer(id, function(input, output, session) {
ns <- session$ns
output$dat_output <- renderTable(head(df_mtcars))
mydf <- reactive(df_mtcars[df_mtcars$gear == input$GearNumber,])
v_lst_CarName <- eventReactive(mydf(), {paste0("sp_cars_",mydf()$CarName)})
output$plotxy <- renderUI({
req(mydf())
tagList(
selectInput(ns("xvar"), label = "X-axis variable", choices = names(mydf()), selected=names(mydf())[2] ),
selectInput(ns("yvar"), label = "Y-axis variable", choices = names(mydf()), selected=names(mydf())[5] )
)
})
output$v_obj_CarName <- renderTable({mydf()})
output$mylist <- renderPrint(list(v_lst_CarName() ))
output$myplot <- renderPlot({
req(input$xvar,input$yvar)
ggplot(mydf(),aes(x=.data[[input$xvar]], y=.data[[input$yvar]])) + geom_point()
})
})
}
ui <- fluidPage(fluidRow(simpUI("cars")))
server <- function(input, output, session) {
simpServer("cars")
}
shinyApp(ui = ui, server = server)

use a reactiveVal in renderText({})

I have a table where I get a value from the same column but from different rows and I would like to display in a verbatimTextOutput each value without overwriting each time.
I tried to do that but I get an error:
library(shiny)
library(DT)
ui <- fluidPage(
DT::dataTableOutput("table"),
verbatimTextOutput("output", placeholder = TRUE)
)
server <- function(input, output) {
val <- reactiveVal(as.character(dt[input$table_rows_selected, 2]))
o <- reactiveVal(NULL)
observeEvent(input$table_rows_selected, {
o(c(o(), "\n", "You chose :", val()))
output$output <- renderText({ o() })
})
dt <- data.frame("a" = paste("a", 1:10), "b" = paste("b", 1:10))
output$table <- DT::renderDataTable({
DT::datatable(dt, selection = list(target = 'row'))
})
}
shinyApp(ui = ui, server = server)
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.)

Can I call a function (not a module) from within shiny modile- can I pass the module reactive values as arguments of function

I am pretty new to Shiny modules.
I am trying to call a function (not a module) from one of my modules.
I would like to pass in the contents of my current reactive values (in my module) as arguments into the function.
The function make a sql query command based on the mrn number, startdate and enddate that is supposed to be fed into the function from 'modemtab'.
this is the error that I get:
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.)
I understand that this is because I'm passing the reactive values not contents of them into the function. My question is how can I pass the contents of these reactive values.
I included a piece of my code here.
Thanks.
app_server <- function(input, output,session) {
.
.
# getting csvupload_values from the first module and feeding it into the next.
csvupload_values <- callModule(csvupload, 'csv-upload')
callModule(modemtab,'mrntab', csvupload_values)
modemtab <- function(input, output, session, csvupload_values){
# the ouput$query is made in the UI part, but it's not the cause of issue.
output$query <- renderText({
if(!is.null(csvupload_values$file_uploaded())){
make_query(mrns = csvupload_values$file_uploaded()$mrns,
startDate = csvupload_values$dates()[1],
endDate = csvupload_values$dates()[2])
}
#This is the function called from within the second module (modemtab)
#this function is saved as a separate file in R folder
make_query <- function(...){
glue_sql("
select *
FROM table
WHERE
rgn_cd = {`rgn_cd`}
AND prdct_lne_cd = {`lob`}
AND ENCTR_STRT_TS >= {`startDate`}
AND ENCTR_END_TS <= {`endDate`}
"
,...
,.con = DBI::ANSI())
}
csvuploadUI <- function(id){
ns <- NS(id)
tagList(
fileInput(ns('file'), "Choose CSV File",
accept = c("text/csv",
"text/comma-separated-values,text/plain",
".csv")),
dateRangeInput(
ns('mrn_date_range'), label = 'Select the range of date:',
start = NULL, end = NULL, min = NULL,
max = NULL, format = "mm/dd/yyyy",
startview = "month", weekstart = 0,
language = "en", separator = " to ", width = NULL),
# Input: Checkbox if file has header ----
checkboxInput(ns('header'), "Header", TRUE)
)
}
# Module Server
csvupload <- function(input, output, session){
userFile <- reactive({
# If no file is selected, don't do anything
validate(need(input$file, message = FALSE))
input$file
})
dataframe <- reactive({
read.csv(userFile()$datapath,
header = input$header)
})
Here:
csvupload_values <- callModule(csvupload, 'csv-upload')
callModule(modemtab,'mrntab', csvupload_values)
csvupload_values is a reactive conductor, so you can't do callModule(modemtab,'mrntab', csvupload_values) outside of a reactive context. You can do:
server <- function(input, output,session) {
csvupload_values <- callModule(csvupload, 'csv-upload')
observeEvent(csvupload_values(),{
if(!is.null(csvupload_values())){
callModule(modemtab, 'mrntab', csvupload_values)
}
})
}
Now, csvupload_values() is a dataframe once you have uploaded the file, so I don't understand why you do csvupload_values$file_uploaded(). Here is a full example:
modemtabUI <- function(id){
ns <- NS(id)
textOutput(ns("query"))
}
modemtab <- function(input, output, session, csvupload_values){
output$query <- renderText({
colnames(csvupload_values())
})
}
csvuploadUI <- function(id){
ns <- NS(id)
tagList(
fileInput(ns('file'), "Choose CSV File",
accept = c("text/csv",
"text/comma-separated-values,text/plain",
".csv")),
checkboxInput(ns('header'), "Header", TRUE),
dateRangeInput(
ns('mrn_date_range'), label = 'Select the range of date:',
start = NULL, end = NULL, min = NULL, max = NULL, format = "mm/dd/yyyy",
startview = "month", weekstart = 0,
language = "en", separator = " to ", width = NULL)
)
}
csvupload <- function(input, output, session){
userFile <- reactive({
# If no file is selected, don't do anything
validate(need(input$file, message = FALSE))
input$file
})
reactive({
read.csv(userFile()$datapath, header = input$header)
})
}
ui <- fluidPage(
csvuploadUI("csv-upload")
modemtabUI("mrntab")
)
server <- function(input, output,session) {
csvupload_values <- callModule(csvupload, 'csv-upload')
observeEvent(csvupload_values(),{
if(!is.null(csvupload_values())){
callModule(modemtab, 'mrntab', csvupload_values)
}
})
}
shinyApp(ui, server)

How to add dynamic contents under those tabs create using renderUI in shinyR application

I am building an application in shiny R where in required tabs can be selected by the users and data relevant to those tabs will be displayed under it.
For example, in below sample application, mtcars data in .csv will be accepted as input parameter.User can select required column names in tabs field.Those colmns will be created as tabs.
Now, I want to show data pertaining to that column from .csv in the appropriate tab.Say, data from the column 'mpg' will be shown under 'mpg' tab.
But i am stuck here.Appreciate someone could tell me a way to display data from relevant column under appropriate tab ,dynamically.
Sample codes used is shown below:
write.csv(mtcars,'mtcars.csv')
#
library(shiny)
library(plyr)
library(dplyr)
ui <- pageWithSidebar(
headerPanel = headerPanel('data'),
sidebarPanel = sidebarPanel(fileInput(
'mtcars', h4('Uplaodmtcardata in csv format')
),
uiOutput('tabnamesui')),
mainPanel(uiOutput("tabsets"))
)
server <- function(input, output, session) {
mtcarsFile <- reactive({input$mtcars})
xxmtcars <-
reactive({
read.table(
file = mtcarsFile()$datapath,
sep = ',',
header = T,
stringsAsFactors = T
)
})
tabsnames <- reactive({
names(xxmtcars())
})
output$tabnamesui <- renderUI({
req(mtcarsFile())
selectInput(
'tabnamesui',
h5('Tab names'),
choices = as.list(tabsnames()),
multiple = T
)
})
tabnamesinput <- reactive({
input$tabnamesui
})
output$tabsets <- renderUI({
req(mtcarsFile())
tabs <-
reactive({
lapply(tabnamesinput(), function(x)
tabPanel(title = basename(x), dataTableOutput(x)))
})
do.call(tabsetPanel, c(tabs()))
})
output[['mpg']] <-
renderDataTable(as.data.frame(select(xxmtcars(), mpg)))#HOW TO AVOID THIS HARD CODING..?BASED ON THE TAB NAME DATA FROM RELEVANT COLUMN IN THE CSV TO BE RETURNED.
}
runApp(list(ui = ui, server = server))
#
Try this
library(shiny)
library(plyr)
library(dplyr)
library(rlang)
library(DT)
ui <- pageWithSidebar(
headerPanel = headerPanel('data'),
sidebarPanel = sidebarPanel(fileInput(
'mtcars', h4('Uplaodmtcardata in csv format')
),
uiOutput('tabnamesui')),
mainPanel(uiOutput("tabsets"))
)
server <- function(input, output, session) {
mtcarsFile <- reactive({input$mtcars})
xxmtcars <-
reactive({
read.table(
file = mtcarsFile()$datapath,
sep = ',',
header = T,
stringsAsFactors = FALSE
)
})
tabsnames <- reactive({
names(xxmtcars())
})
output$tabnamesui <- renderUI({
req(mtcarsFile())
selectInput(
'tabnamesui',
h5('Tab names'),
choices = as.list(tabsnames()),
multiple = T
)
})
tabnamesinput <- reactive({
input$tabnamesui
})
output$tabsets <- renderUI({
req(mtcarsFile())
tabs <-
reactive({
lapply(tabnamesinput(), function(x)
tabPanel(title = basename(x), dataTableOutput(x)))
})
do.call(tabsetPanel, c(tabs()))
})
observe(
lapply(tabnamesinput(), function(x) {
output[[x]] <- DT::renderDataTable({
t<-as.data.frame(dplyr::select(xxmtcars(), !! sym(x)) )
print(t)
datatable(t)
})
})
)
}
runApp(list(ui = ui, server = server))