The issue here is a slightly complicated one. The ignoreInit parameter has an effect only for the reactive flush cycle in which the observer is created. However, with the way that your code triggers the re-execution of the renderUI for output$more_buttons, the observeEvents for the buttons are created in one flush cycle, but the values trigger their execution in a later flush cycle.
Here's roughly what's happening:
- In the browser, the user clicks on the
add_buttons button.
- The server session receives the incremented value for
input$add_buttons. This triggers a reactive flush cycle.
- In the flush cycle, the code in the
observeEvent(input$add_buttons, ...) executes. This creates the new slider input objects and stores them in rvs$buttons -- but does not send them to the browser!. It also calls observeEvent() for input$button1, input$button2, and so on. Those observers execute immediately in this flush cycle (I think they do, at least -- though it's possible they execute in the next one) and but because of ignoreInit=TRUE, they don't execute the code in the body; however, they are marked as initialized at this point. The reactive flush cycle ends.
- Because
rv$buttons has changed, this triggers another reactive flush cycle. It causes the renderUI for output$more_buttons to execute, and sends the new slider UI stuff to the browser. This flush cycle ends.
- The client receives the UI for the sliders and renders them. This creates new input values for the sliders, so the client sends the new slider values to the server.
- The server receives the slider values, and this triggers the observers for the sliders (
input$buttons1, etc.) Because these observers were earlier marked as initialized, the code in the observer body executes.
In this situation, where the input value is received after the observer is created, you can add a variable to record whether it has run before. For example:
has_run <- FALSE
observeEvent(input$x, {
if (!has_run) {
has_run <<- TRUE
return()
}
# Do stuff here...
})
Here's a modified version of your app that does that:
library(shiny)
ui <- fluidPage(
fluidRow(actionButton(inputId = "add_buttons", label = "Add 5 Sliders")),
uiOutput("more_buttons") # this is where the dynamically added buttons will go.
)
server <- function(input, output, session) {
rvs <- reactiveValues(observers = list(), buttons = list())
observeEvent(input$add_buttons,{
cat("observeEvent(input$add_buttons, ...)\n")
l <- length(rvs$buttons) + 1
for(i in l:(l+4)) {
rvs$buttons[[i]] <- sliderInput(
paste0("button", i), paste0("Slider ", i),
min = 1, max = 20, value = i
)
}
lapply(l:(l+4), function(i) {
has_run <- FALSE
observeEvent(
{
# In this event expression, we print out a message
cat("event input$", paste0("button", i), ": ")
print(input[[paste0("button",i)]])
input[[paste0("button",i)]]
},
{
cat("inside observeEvent(input$", paste0("button", i), ", ...): ")
print(input[[paste0("button",i)]])
if (!has_run) {
has_run <<- TRUE
return()
}
if (!identical(rvs$observers[i], input[[paste0("button",i)]])) {
print(sprintf("You used the slider number %d",i))
} else {
rvs$observers[i] <- input[[paste0("button",i)]]
}
}
)
})
})
output$more_buttons <- renderUI({
cat("output$more_buttons\n")
do.call(fluidRow, rvs$buttons) # Add the dynamic buttons into a single fluidRow
})
}
shinyApp(ui, server)