Martijn Tennekes
Martijn Tennekes

Reputation: 2051

How to wait for input widget updates until rendering output in Shiny?

See the following nonsense minimal-working example that resembles a problem:

library(shiny)
ui = shiny::fluidPage(

    shiny::sidebarLayout(
        shiny::sidebarPanel(
            shiny::radioButtons("type", "Type", choices = c("5", "15", "21")),
            shiny::sliderInput("x", "x", min = 1, max = 10, value = 7),
            shiny::checkboxInput("auto","Set y to 8 when type is 15, and 9 when type is 21", value = TRUE),
            shiny::sliderInput("y", "y", min = 1, max = 10, value = 7)
        ),

        shiny::mainPanel(
            shiny::plotOutput("show")
        )
    )
)
server = function(input, output, session) {
    plotData = shiny::reactive({
        list(x = input$x, y = input$y, type = input$type)
    })

    output$show = renderPlot({
        shiny::req(plotData())
        d = plotData()
        print("refresh")
        plot(d$x,d$y, pch = as.integer(d$type))
    })


    observe({
        n = input$n
        a = input$auto
        type = input$type

        if (type == "a") return(NULL)
        if (a) shiny::updateSliderInput(session, "y", value = switch(type, "15" = 8, "21" = 9))
    })
}
shiny::shinyApp(ui = ui, server = server)

The problem is that the plot is rendered twice when the user selects either type 15 or 21. That is caused by the fact that when plotData changes, updateSliderInput is executed before renderPlot. (I have seen this behavior with the reactlog package.) However, I don't know how to solve this. I've tried a bunch of functions, such as isolate, req, but without success.

Upvotes: 3

Views: 1942

Answers (1)

ismirsehregal
ismirsehregal

Reputation: 33417

To avoid triggering reactives or outputs unnecessarily you should almost alway use freezeReactiveValue when using an update* function in :

library(shiny)

ui = shiny::fluidPage(
  shiny::sidebarLayout(
    shiny::sidebarPanel(
      shiny::radioButtons("type", "Type", choices = c("5", "15", "21")),
      shiny::sliderInput("x", "x", min = 1, max = 10, value = 7),
      shiny::checkboxInput("auto","Set y to 8 when type is 15, and 9 when type is 21", value = TRUE),
      shiny::sliderInput("y", "y", min = 1, max = 10, value = 7)
    ),
    shiny::mainPanel(
      shiny::plotOutput("show")
    )
  )
)

server = function(input, output, session) {
  plotData = shiny::reactive({
    list(x = input$x, y = input$y, type = input$type)
  })
  
  observe({
    n = input$n
    a = input$auto
    type = input$type
    
    if (type == "a") return(NULL)
    freezeReactiveValue(input, "y")
    if (a) shiny::updateSliderInput(session, "y", value = switch(type, "15" = 8, "21" = 9))
  }, priority = 1)
  
  output$show = renderPlot({
    shiny::req(plotData())
    d = plotData()
    print("refresh")
    plot(d$x,d$y, pch = as.integer(d$type))
  })
}

shiny::shinyApp(ui = ui, server = server)

Please see this related chapter from Mastering Shiny.

Upvotes: 4

Related Questions