Open Kurt17P opened 5 years ago
I usually create a list (reactive) with default values for the UI items. In the event observers I check whether the values have changed (they shouldn't upon initialisation). If changed, consecutive actions are triggered. There might be a more elegant solution to this, however it works fine for me.
library(shiny)
library(rlang)
ui <- fluidPage(
checkboxInput("staticBox", "static"),
uiOutput("body"),
textOutput("text")
)
server <- function(input, output, session) {
# Buttons initial state
buttons <- list(dynamicBox = FALSE, staticBox = FALSE, slider = 5)
# Default Values (reactive not required in this minimal example)
rv <- exec(reactiveValues, !!!buttons)
# Render UI
output$body <- renderUI(
tagList(
checkboxInput("dynamicBox", "dynamic"),
sliderInput("slider", value = 5, min = 0, max = 10, step = 1, label = "Slider")
))
# Build event observers in a loop
sapply(names(buttons), function(i){
observeEvent(input[[i]], {
if(rv[[i]] != input[[i]]){
print(sprintf("observeEvent of %s is executed", i))
output$text <- renderText({sprintf("observeEvent of %s is executed", i)})
}
rv[[i]] <- input[[i]]
})
})
}
shinyApp(ui, server)
# Example
# observeEvent(input$dynamicBox, {
# if(rv$dynamicBox != input$dynamicBox)
# cat("observeEvent of dynamic checkBox is executed\n")
# rv$dynamicBox <- input$dynamicBox
# })
# observeEvent(input$staticBox, {
# if(rv$staticBox != input$staticBox)
# cat("observeEvent of static checkBox is executed\n")
# rv$staticBox <- input$staticBox
# })
I am affected by this issue as well and keeping track of the initial trigger worked for me. I wonder, if this can be easily fixed on the Shiny side.
Tricky issue! ignoreInit
is meant to ignore the initial call of the observer itself, not of the UI it’s referring to. But it is unintuitive indeed that it would work one way with static inputs and another with dynamic.
I do think this issue deserves a formal solution of some kind, but in the meantime a more convenient wrapper for @m-saenger’s “only invalidate when changed” approach is documented here: https://github.com/daattali/advanced-shiny/blob/master/reactive-dedupe/README.md
I’m not 100% sure this solves it without modification but hopefully provides a starting point.
For me a convenient solution to this problem is to wrap eventExpr
inside req
. When silent req
exception is raised inside eventExpr
, observer is not executed, but what is more important it does not count as a call to the observer, specifically at the beginning it does not count as initialization. As a result, the observer is called for the first time when req
gets truthy value for the first time, and if ignoreInit = TRUE
, the observer is not executed during this first call. I believe that this is what OP was looking for. And it does work as well if input is generated directly, not in dynamic UI. Tested with shiny 1.7.4
.
To be honest I'm not sure whether this is a desired behaviour, at least it is not documented (I didn't find anything), I figured it out partially by accident. Maybe @jcheng5 could shed more light on this.
Actual expression inside req
depens on input type, for most of them it could be straightforward req(input)
, for checkBoxInput
however it has to be req(!is.null(input))
. Complete example from OP below.
library(shinydashboard)
library(shiny)
library(shinyjs)
ui <- dashboardPage(
dashboardHeader(title = "observeEvent ignoreInit"),
dashboardSidebar(),
dashboardBody(
useShinyjs(),
checkboxInput("staticBox", "static"),
uiOutput("body"))
)
server <- function(input, output, session) {
output$body <- renderUI( tagList(
checkboxInput("dynamicBox", "dynamic"),
checkboxInput("dynamicBoxDelayed", "dynamic delayed")
))
# observe static checkBox
observeEvent(input$staticBox, {
cat("observeEvent of static checkBox is executed\n")
}, ignoreInit=TRUE)
# observe dynamic checkBox
observeEvent(req(!is.null(input$dynamicBox)), {
# this is executed upon start, although ignoreInit is set to TRUE
cat("observeEvent of dynamic checkBox is executed\n")
cat( paste0("dynamicBox value = '", input$dynamicBox, "'\n") )
}, ignoreInit=TRUE)
# observe dynamicDelayed checkBox
shinyjs::delay(100, {
observeEvent(input$dynamicBoxDelayed, {
cat("observeEvent of dynamic checkBoxDelayed is executed\n")
}, ignoreInit=TRUE)
})
}
shinyApp(ui, server)
Hi, I recognized that
observeEvent
is triggered upon start when the UI is created dynamically, even withignoreInit=TRUE
. I found this thread on stackoverflow, but the presented solution does not work forcheckboxInput
because the value of theeventExpr
is eitherT/F
, so it's not possible to decide if it's the first call during initialization or a regular call.In the minimal working example below I found a workaround by using
shinyjs::delay
, but I am not sure if this would always work and what minimum delay time is required.To me,
ignoreInit
should also work for dynamic UIs, i.e. the observer fordynamicBox
should not be triggered upon initialization as is the case forstaticBox
. Is there any chance to fix that issue or is this expected behavior?