I'm having a problem with Shiny and I don't know if it's a bug or I'm missing something. I tried to find a solution but I can't seem to find references to this problem online.
I have a function that is supposed to run when I press a button, but it's only running when the user is looking at the output after the button is pressed.
It's somewhat weird, so I created a minimal example. I have two tabs. In the first, I choose a number (mean), and in the second tab I plot values around that mean. The button is in the first tab, but the "create.plot" function only starts to run when you go to tab2. To make things clear, I created this 'loading.div', which shows up when the function is running.
library(shiny)
library(shinyjs)
create.plot <- function(mean)
{
shinyjs::show('loading.div')
y <- rep(NA,10)
for(i in 1:10)
{
y[i] <- rnorm(1,mean=mean)
Sys.sleep(0.5)
}
shinyjs::hide('loading.div')
return(list(y=y, test='Now it runs from tab1'))
}
server <- function(input, output, session) {
run.function <- eventReactive(input$run,create.plot(input$mean))
output$plot.y <- renderPlot({
plot(run.function()[['y']])
})
output$test <- renderText({
run.function()[['test']]
})
}
ui <- fluidPage(shinyjs::useShinyjs(),
hidden(div(id='loading.div',h3('Loading..'))),
tabsetPanel(
tabPanel('tab1',
numericInput(inputId = 'mean',label='Mean', value=1),
actionButton(inputId = 'run', label='Run')
#,textOutput('test') ## Uncommenting this line, it works.
),
tabPanel('tab2',
plotOutput('plot.y'))
)
)
shinyApp(server=server,ui=ui)
To make sure that the problem was that the user was not looking at the output when at tab1, I created a test string. If you uncomment that line, the problem is "fixed": the function runs directly after the user hits the button. However, that only works because he is looking at an output in tab1.
This is becoming a serious problem because I have a shiny dashboard with many tabs. There are tabs with inputs and tabs with outputs. I'd like the function to run when I click a button, but it's only running when the user goes to the "outputs" tab.
Is this a known problem? Are there workarounds?
A reactive expression is an R expression that uses widget input and returns a value. The reactive expression will update this value whenever the original widget changes. To create a reactive expression use the reactive function, which takes an R expression surrounded by braces (just like the render* functions).
You can create reactive output with a two step process. Add an R object to your user interface. Tell Shiny how to build the object in the server function. The object will be reactive if the code that builds it calls a widget value.
An expression that returns a Shiny tag object, HTML() , or a list of such objects. env. The parent environment for the reactive expression. By default, this is the calling environment, the same as when defining an ordinary non-reactive expression.
2.2. 1 Common structure. All input functions have the same first argument: inputId . This is the identifier used to connect the front end with the back end: if your UI has an input with ID "name" , the server function will access it with input$name .
Actuall that is the way it is supposed to work. That is what being "reactive" is all about - it does lazy evaluation with caching (so you do not have to). If you want to force something to be calculated, put it in a reactiveValue
and calculate it with observeEvent
(this does eager execution), and then display the values with reactive
blocks.
I modified your example to do that here:
library(shiny)
library(shinyjs)
create.plot <- function(mean) {
shinyjs::show('loading.div')
y <- rep(NA,10)
for (i in 1:10) {
y[i] <- rnorm(1,mean = mean)
Sys.sleep(0.5)
}
shinyjs::hide('loading.div')
return(list(y = y,test = 'Now it runs from tab1'))
}
server <- function(input,output,session) {
rv <- reactiveValues(plotvals=NULL)
observeEvent(input$run,{rv$plotvals <- create.plot(input$mean) })
output$plot.y <- renderPlot({
req(input$run)
plot(rv$plotvals[['y']])
})
output$test <- renderText({
rv$plotvals[['test']]
})
}
ui <- fluidPage(shinyjs::useShinyjs(),
hidden(div(id = 'loading.div',h3('Loading..'))),
tabsetPanel(
tabPanel('tab1',
numericInput(inputId = 'mean',label = 'Mean',value = 1),
actionButton(inputId = 'run',label = 'Run')
#,textOutput('test') ## Uncommenting this line, it works.
),
tabPanel('tab2',
plotOutput('plot.y'))
)
)
shinyApp(server = server,ui = ui)
Yielding this (right after pressing the run button):
And then when that text goes away, you can see the plot in tab2 immediately.
To better understand this stuff, and the lazy nature of reactives and the eager nature of observe
, see Joe Cheng's presentation on it here. It is well worth the time:
https://www.rstudio.com/resources/webinars/shiny-developer-conference/
In fact the reactive nature of Shiny is exactly what makes it so productive, once you get used to it - it is rather different.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With