I am trying to make Shiny App which allows users to save inputs and later load them.
Easiest way to approach this, is to make Save button, which saves inputs. Here is basic app to demonstrate:
server.R
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
sliderInput("integer", "Integer:",
min = 0, max = 1000,
value = 500)
),
mainPanel(tableOutput("values"),
actionButton('save_inputs', 'Save inputs')
)
))
server <- function(input, output, session) {
sliderValues <- reactive({
value = input$integer
})
output$values <- renderTable({
sliderValues()
})
observeEvent(input$save_inputs,{
saveRDS( input$integer , file = 'integer.RDS')
})
}
shinyApp(ui = ui, server = server)
However, I would like to make saving automatic, e.g. I want inputs to be saved at end of session. onSessionEnded() should be answer to this, but it can't reach input values and save them.
session$onSessionEnded( function() {
saveRDS( input$integer, file = 'integer.RDS')
})
Which returns error: 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.)
Is there any way to solve it?
Using isolate seems to solve the problem.
session$onSessionEnded(function() {
isolate(saveRDS( input$integer, file = 'integer.RDS'))
})
Using another observe event function and watching the value of isClosed() we can
make this work
observeEvent(session$isClosed()==T,{
saveRDS( input$integer, file = 'integer.RDS')
})
observeEvent() as well as reactive() are both considered "reactive" environments which means they are watching for changing values throughout the session and not just on startup. If you put a function that needs to be reactive outside of a reactive environment shiny will do you the favor of sending you that error, to inform you the function would never be called unless we wrap it in a reactive function.
Also +1 for the well composed question.
Related
I have a Shiny app that calls several custom functions in response to a click event. These custom functions make use of multiple reactive values and I didn't think I would need to pass all of those reactive values as arguments to the custom functions but it seems like I do.
I would have expected the app to behave like normal R where a custom function will search the immediate environment, then, upon not finding a variable, will search the enclosing environment and up the scope, only throwing an error if that variable's undefined at every level. Instead, when the function deals with reactive variables, it seems like code within the function is unaware of reactive variables defined outside of it. Is this true?
A quick demo app that crashes because identity_fun cannot find input$click:
library(shiny)
identity_fun <- function(x){
print(paste("Title's been changed", input$click, "times now"))
x
}
ui <- fillPage(
sidebarLayout(
sidebarPanel(
textInput("text", "Plot title here"),
actionButton("click", "Click when ready to apply it")
),
mainPanel(
plotOutput("mainplot")
)
)
)
server <- function(input, output){
input_text <- reactiveVal()
observeEvent(input$click, {
input_text(identity_fun(input$text))
})
output$mainplot <- renderPlot({
plot(1, main = input_text())
})
}
shinyApp(ui, server)
In base R, a variable outside the function is found trivially:
input <- list()
input$click <- 1
identity_fun("blah")
[1] "Title's been changed 1 times now"
[1] "blah"
and this different behavior took me by surprise when working with a Shiny app.
To fix the above app, I can pass the relevant information as an argument to identity_fun
identity_fun <- function(x, input_click){
print(paste("Title's been changed", input_click, "times now"))
x
}
and
observeEvent(input$click, {
input_text(identity_fun(input$text, input$click))
})
but I'm wondering if that's the best way of doing it. I realize this is probably intentional behavior because it seems complicated for the function to auto-detect that it uses input$click and invalidate if input$click changes, but Shiny's been magic to me before.
Is there a better way of passing reactive values to a function than by adding them as arguments?
The issue with you above example is, that identity_fun is defined outside of the server function. Shiny's input however is only available inside of the server function (there is no input variable in the global env. - you can check this e.g. via RStudio's environment tab).
The following works:
library(shiny)
ui <- fillPage(
sidebarLayout(
sidebarPanel(
textInput("text", "Plot title here"),
actionButton("click", "Click when ready to apply it")
),
mainPanel(
plotOutput("mainplot")
)
)
)
server <- function(input, output){
identity_fun <- function(x){
print(paste("Title's been changed", input$click, "times now"))
x
}
input_text <- reactiveVal()
observeEvent(input$click, {
print(identity_fun(input$text))
})
output$mainplot <- renderPlot({
plot(1, main = input_text())
})
}
shinyApp(ui, server)
Accordingly functions defined within Shiny apps work just like they do everywhere else in R.
However, I'd recommend to always pass function parameters explicitly to make your code more readable.
Please also check this article.
At the moment I am attempting the following: import a file in Rshiny, give it a number (interactive), and then move on to the next file. This part works fine. However, I would also like to store the data of every iteration, and then show it on the user interface.
However, it is not working. So I guess something is not right with the reactivity, but I am not sure how to fix it.
ui<-fluidPage(
mainPanel(
radioButtons(inputId="score",label="Give a score",choices=c(1:9),selected=1),
actionButton(inputId="new","Next file"),
tableOutput("savdat")
)
)
server<-function(input,output){
NoFiles<-length(list.files())
Here an empty reactive data.frame
outputdata<-reactive(data.frame("file"="file","score"="score"))
filename<-eventReactive(input$new,{
WhichFile<-sample(1:NoFiles,1)
filename<-list.files()[WhichFile]
return(filename)
})
scores<-eventReactive(input$new,{
return(input$score)
})
Then I would like to append the previous values of the outputdata, with the new values. But it is not working
outputdata<-eventReactive(input$new,{
rbind(outputdata(),filename(),scores())
})
output$savdat<-renderTable(outputdata())
}
shinyApp(ui, server)
Any advice would be welcome
It appears you want the reactivity to occur each time you click on the 'Next file' button. I rewrote your code to respond just once, using 'ObserveEvent', each time the 'Next file' button is clicked. The 2nd challenge is permitting values to persist upon each reactive event. While there are multiple ways to handle this, I chose an expedient technique, the '<<-' assignment statement, to permit the variable 'output data' to persist (this is generally not a good programming technique). Because the variable 'outputdata' exists in all environments, you'll need to wipe your environment each time you want to run this program.
Here's my rewrite using the same ui you created:
ui<-fluidPage(
mainPanel(
radioButtons(inputId="score",label="Give a score",choices=c(1:9),selected=1),
actionButton(inputId="new","Next file"),
tableOutput("savdat")
)
)
server<-function(input,output){
NoFiles<-length(list.files())
setupData <- function(filename,score) {
data <- data.frame(filename,score,stringsAsFactors = FALSE)
return(data)
}
observeEvent (input$new, {
WhichFile<-sample(1:NoFiles,1)
filename<-list.files()[WhichFile]
if (!exists(c('outputdata'))) {
score <- input$score
outputdata <<- data.frame (filename,score,stringsAsFactors = FALSE)
}
else {
outputdata <<- rbind(outputdata,setupData(filename,input$score))
}
# Show the table
output$savdat<-renderTable(outputdata)
})
}
shinyApp(ui, server)
I'm trying to debug my shiny dashboard
For several render* function, I need to debug them with some log (with print or cat) but I can't use those function inside a renderDataTable() / renderText()
for example:
output$selectedData = renderDataTable(
myCsv[which(myCsv[[myCase_id]]==input$process_tokens),]
)
I would like to print something to the console before and after the instruction of renderDataTable() but
output$selectedData = renderDataTable(
cat("rendering...")
myCsv[which(myCsv[[myCase_id]]==input$process_tokens),]
cat("rendered")
)
How can I do this ?
Here is a possible solution to the problem. First I use a variable called data to assingn any calculations to, in your case
data<-myCsv[which(myCsv[[myCase_id]]==input$process_tokens),]. This is used inside the render function and will be created when the output is rendered since it relies on this. I then use an observe function that requires the variable data to be created before printing the second "rendered" to the console. That works once on startup, and will work fine if your data is constant. If you have changing data, for my example the data changes with a user selection, we will have to re-render the table. Since the render function is reactive and you are using input$process_tokens, the render function will re-run when the input changes. In this example it runs when input$select changes. When it runs it resets the variable data to NULL, and we trigger a separate observeEvent that monitors changes to input$select(input$process_tokens). This observeEvent also requires data before continuing, and since the render function set it to null it will not print the second "rendered" until data is created, just as in the first case.
library(shiny)
library(DT)
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
selectizeInput("select","select",choices=(c(1,2,3,4)))
),
mainPanel(
dataTableOutput("selectedData")
)
))
server <- function(input, output, session) {
data<-reactive({data.frame(input$select,4,5)})
output$selectedData <- renderDataTable({
data<-NULL
print("rendering..")
data<-datatable(data())
})
#Observe inital rendering (only needed if no change to data)
observe({
req(data)
print("rendered!")
})
#Observe Changes to data
observeEvent(input$select,{
req(data)
print("rendered!")
})
}
shinyApp(ui, server)
Specific code for you:
server <- function(input, output, session) {
output$selectedData <- renderDataTable({
data<-NULL
print("rendering..")
data<- myCsv[which(myCsv[[myCase_id]]==input$process_tokens),]
})
#Observe inital rendering (only needed if no change to data)
observe({
req(data)
print("rendered!")
})
#Observe Changes to data
observeEvent(input$process_tokens,{
req(data)
print("rendered!")
})
}
shinyApp(ui, server)
Note that you will get two "rendered" printouts when the program initially starts, this is b/c both the observe and observeEvent run since both conditions are met. If your data does change with input$process_tokens, then you can get rid of the observe function, and only use the observeEvent. If your data does not change and the table is only rendered once, then get rid of the observeEvent. I was trying to cover all bases.
I have a rather complex Shiny application and something weird happens:
When I print out some of my intermediate steps the App makes, everything gets printed out twice. That means, everything gets evaluated etc. twice.
I know without seeing the progamme its rather hard to tell what causes the problem, but maybe someone can pin point me (based on experierence/knowledge) what might be the problem.
Like I mentioned in the comment, isolate() should solve your problem.
Beyond the documentation of Rstudio http://shiny.rstudio.com/articles/reactivity-overview.html
I recommend the following blog article for interesting informations beyond the RStudio docu.
https://shinydata.wordpress.com/2015/02/02/a-few-things-i-learned-about-shiny-and-reactive-programming/
In a nutshell, the easiest way to deal with triggering is to wrap your code in isolate() and then just write down the variables/inputs, that should trigger changes before the isolate.
output$text <- renderText({
input$mytext # I trigger changes
isolate({ # No more dependencies from here on
# do stuff with input$mytext
# .....
finishedtext = input$mytext
return(finishedtext)
})
})
Reproducible example:
library(shiny)
ui <- fluidPage(
textInput(inputId = "mytext", label = "I trigger changes", value = "Init"),
textInput(inputId = "mytext2", label = "I DONT trigger changes"),
textOutput("text")
)
server <- function(input, output, session) {
output$text <- renderText({
input$mytext # I trigger changes
isolate({ # No more dependencies from here on
input$mytext2
# do stuff with input$mytext
# .....
finishedtext = input$mytext
return(finishedtext)
})
})
}
shinyApp(ui, server)
I encountered the same problem when using brush events in plotOutput. The solution turned out to be resetOnNew = T when calling plotOutput to prevent changes in my plot causing the brush event to be evaluated again.
I am running into an issue because observe is being called first before the UI loads.
Here is my ui.R
sidebarPanel(
selectInput("Desk", "Desk:" , as.matrix(getDesksUI())),
uiOutput("choose_Product"), #this is dynamically created UI
uiOutput("choose_File1"), #this is dynamically created UI
uiOutput("choose_Term1"), #this is dynamically created UI ....
Here is my Server.R
shinyServer(function(input, output,session) {
#this is dynamic UI
output$choose_Product <- renderUI({
selectInput("Product", "Product:", as.list(getProductUI(input$Desk)))
})
#this is dynamic UI
output$choose_File1 <- renderUI({
selectInput("File1", "File 1:", as.list(getFileUI(input$Desk, input$Product)))
})
#this is dynamic UI and I want it to run before the Observe function so the call
# to getTerm1UI(input$Desk, input$Product, input$File1) has non-null parameters
output$choose_Term1 <- renderUI({
print("Rendering UI for TERM")
print(paste(input$Desk," ", input$Product, " ", input$File1,sep=""))
selectInput("Term1", "Term:", getTerm1UI(input$Desk, input$Product, input$File1))
})
This is my observe function and it runs before the input$Product and input$File1 are populated so I get an error because they are both NULL. But I need to use the input from the UI.
observe({
print("in observe")
print(input$Product)
max_plots<-length(getTerm2UI(input$Desk, input$Product, input$File1))
#max_plots<-5
# Call renderPlot for each one. Plots are only actually generated when they
# are visible on the web page.
for (i in 1:max_plots ) {
# Need local so that each item gets its own number. Without it, the value
# of i in the renderPlot() will be the same across all instances, because
# of when the expression is evaluated.
local({
my_i <- i
plotname <- paste("plot", my_i, sep="")
output[[plotname]] <- renderPlot({
plot(1:my_i, 1:my_i,
xlim = c(1, max_plots ),
ylim = c(1, max_plots ),
main = paste("1:", my_i, ". n is ", input$n, sep = "") )
})
})
}##### End FoR Loop
},priority = -1000)
Any idea how to get the input$Product and input$File1 to be populated BEFORE observe runs?
Thank you.
EDIT: Scroll down to TClavelle's answer for a better solution. While this answer has the most upvotes, I wrote it when Shiny had fewer features than it does today.
The simplest way is to add an is.null(input$Product) check at the top of each observe, to prevent it from running before the inputs it uses are initialized.
If you don't want your observers to do the null-check each time they're run, you can also use the suspended = TRUE argument when registering them to prevent them from running; then write a separate observer that performs the check, and when it finds that all inputs are non-null, calls resume() on the suspended observers and suspends itself.
You need to use the Shiny Event Handler and use observeEvent instead of observe. It seems to be about the only way to get rid of the "Unhandled error" message caused by NULL values on app startup. This is so because unlike observe the event handler ignores NULL values by default.
So your observe function could end up looking something like this (no need for priorities, or resume/suspended etc!)
observeEvent(input$Product, ({
max_plots<-length(getTerm2UI(input$Desk, input$Product, input$File1))
... (etc)
})# end of the function to be executed whenever input$Product changes
)
I could not copy paste your example code easily to make it run, so I'm not entirely sure what your full observe function would look like.
You can use req() to "require" an input before a reactive expression executes, as per the Shiny documentation here: https://shiny.rstudio.com/articles/req.html and the function documentation here: https://shiny.rstudio.com/reference/shiny/latest/req.html
e.g.
observeEvent({
req(input$Product)
req(input$File1)
# ...
})
We'd need an MRE to provide a working answer, but, assuming you need input$Product and input$File1, but do not want to take a dependency on them, only on input$Desk, you could:
observe({
product <- isolate(input$Product)
file1 <- isolate(input$File1)
print("in observe")
print(product)
max_plots<-length(getTerm2UI(input$Desk, product, file1))
for (i in 1:max_plots ) {
# ...
}
})
this is probably effectively equivalent to an observeEvent(input$Desk, ....), but might offer more flexibility.