Search code examples
rshinyreactiveobservers

What is the correct way to implement objects that depend on each others change in Shiny R?


I am trying to use Shiny. I am used to other programming languages (little bit of Java from App Development) where there are onClick Events, that trigger changes of other variables. I know that in MVC after a change in a view element, the controller can change the model variables in the background, then update the view without triggering an infinite loop of events on other elements.

The code here works, but it triggers unnecessary and unwanted loops:

If you choose 49 as n, dozens gets changed to 4, which changes n to 48, which changes dozens to 4, which doesn't trigger a change event.

If you then change n to 49, dozens changes to 4, which doesn't trigger a change event.

It seems wasteful and resource-intensive..

library(shiny)

ui <- fluidPage(
  sliderInput(inputId = "dozens",
              label = "Dozens:",
              min = 1,
              max = 5,
              value = 2),
  sliderInput(inputId = "n",
              label = "n:",
              min = 1,
              max = 60,
              value = 24)
)
server <- function(input, output,session) {
  observeEvent(input$dozens,{
    updateSliderInput(session,"n",value=12*input$dozens)
  })
  observeEvent(input$n,{
    updateSliderInput(session,"dozens",value=round(input$n/12))
  })
}

shinyApp(ui = ui, server = server)

What is the correct way?


Solution

  • observeEvent is (sort of) an antipattern in Shiny. Do not get me wrong, it is very useful, indispensable, I use it all the time. But the reactivity usually works better in Shiny by not caring about it and only writing the computations in a descriptive manner.

    In OP's case, though, I agree it does not seem possible.
    And circular dependencies between inputs are especially tricky.

    A solution that is not generic but could work in OP's case is to add a simple if():

    server <- function(input, output, session) {
      observeEvent(input$dozens, {
        if (round(input$n / 12) != input$dozens) {
          updateSliderInput(session, "n", value = 12 * input$dozens)
        }
      })
    
      observeEvent(input$n, {
        updateSliderInput(session, "dozens", value = round(input$n / 12))
      })
    }