My Shiny app consists of selecting a set of parameters to generate outputs based on the corresponding data, where each parameter is dependent on the previous one to narrow down which data needs to be used. To achieve this I use dynamic inputs with uiOutput
and renderUi
.
An example of the logic would be:
On initialization, a valid set of parameters and the output reports are rendered. However I'd like to add a "Run" button so that the outputs are only rendered when the user completed their selections and manually trigger a recalculation - but the outputs still need to be rendered on initialization.
Here is a minimal reprex I put together with one static selection and one dynamic selection:
library(tidyverse)
library(shiny)
(homes <- tibble(
zone = rep(c("A", "B"), each = 3),
owner = paste0("person", c(1,2,3,4,3,1)),
price = 111000*(1:6)
))
ui <- shiny::fluidPage(
selectInput("sel1", "zone", choices = unique(homes$zone)),
uiOutput("ui_sel2"),
actionButton("confirm", "confirm"),
verbatimTextOutput("txt")
)
server <- function(input,output,session) {
# render dynamic input
output$ui_sel2 <- renderUI({
choices <- dplyr::filter(homes, zone == input$sel1)$owner %>% unique()
selectInput("sel2", "owner", choices = choices)
})
sel_price <- reactive({
# need dynamic input to render before calc
req(input$sel2)
# get price
dplyr::filter(homes, zone == input$sel1, owner == input$sel2)$price[1]
})
# render price from selected zone+owner
output$txt <- renderText({paste("Price of selected home is", sel_price())}) %>%
bindEvent(input$confirm, ignoreNULL=FALSE)
}
shinyApp(ui = ui, server = server)
I tried multiple variations of bindEvent()
, both on the render function and the reactive expression, trying to include both input$sel2
and input$confirm
but wasn't able to achive the render on initialization. The render only works after first clicking the button, after which I get the expected behavior.
I believe it has to do with the req(input$sel2)
I use in my reactive expression to ignore the dynamic until it is rendered, but I would have guessed that adding it in bindEvent()
would have solved the issue - not the case.
I would add a reactive
which returns the filtered choices for the dynamic selectInput
and in your sel_prices
use coalesce
to set it to the first element of this list in case it is NULL
(so basiclaly on startup):
library(tidyverse)
library(shiny)
homes <- tibble(
zone = rep(c("A", "B"), each = 3),
owner = paste0("person", c(1,2,3,4,3,1)),
price = 111000*(1:6)
)
ui <- fluidPage(
selectInput("sel1", "zone", choices = unique(homes$zone)),
uiOutput("ui_sel2"),
actionButton("confirm", "confirm"),
verbatimTextOutput("txt")
)
server <- function(input,output,session) {
get_choices <- reactive({
dplyr::filter(homes, zone == input$sel1) %>%
pull(owner) %>%
unique()
})
output$ui_sel2 <- renderUI({
selectInput("sel2", "owner", choices = get_choices())
})
sel_price <- reactive({
filter(homes, zone == input$sel1,
owner == coalesce(input$sel2, get_choices()[[1L]])) %>%
pull(price)
})
output$txt <- renderText({
paste("Price of selected home is", sel_price())
}) %>%
bindEvent(input$confirm, ignoreNULL = FALSE)
}
shinyApp(ui = ui, server = server)