简体   繁体   中英

R Shiny - Understanding the difference between observe and observeEvent when updating mutually dependent inputs

The app below has two mutually dependent numericInputs, a and b . The value of input$a is 1-input$b and the value of input$b is 1-input$a . Whenever the user changes an input's value, I would like to update the value of the other accordingly. The code below contains two approaches:

  1. A single observer
  2. Two separate observers - one that listens for changes to a and updates b and another that listens to b and updates a .

When I try 1), the inputs get stuck in an infinite loop but I'm not sure I understand why. If the user changes b , then the observer is triggered and the value of a gets updated to be 1-b . Since the values of both inputs are now up-to-date, there are no further changes to input$a or input$b and the observer shouldn't be triggered again until the user makes another change. But this isn't the case.

How exactly does approach 1) differ from approach 2)? Why does 1) get stuck in a loop whereas 2) doesn't?

library(shiny)

shinyApp(
  ui = fluidPage(
    numericInput('a','a', value = 0.5, max = 1, min = 0),
    numericInput('b','b', value = 0.5, max = 1, min = 0)
  ),
  server = function(input, output, session) {

    # Using a single observer ----
    observe({

      updateNumericInput(session, 'a', value = 1-req(input$b))

      updateNumericInput(session, 'b', value = 1-req(input$a))

      print(input$a)

    })

    # Using separate observers ----
    # observeEvent(input$b, {
    #   updateNumericInput(session, 'a', value = 1-input$b)
    # })
    # 
    # observeEvent(input$a, {
    #   updateNumericInput(session, 'b', value = 1-input$a)
    # })

  }
)

In the documentation of observe and observeEvent , it is written:

  • observe

An observer is like a reactive expression in that it can read reactive values and call reactive expressions, and will automatically re-execute when those dependencies change. [...] [O]bservers use eager evaluation; as soon as their dependencies change, they schedule themselves to re-execute.

In your example, observe never stops "observing" and updates the input as soon as the other changes, even if the user doesn't do anything. Therefore, you stay in an infinite loop since each of the two inputs depends on the other.

  • observeEvent

[S]ometimes you want to wait for a specific action to be taken from the user, like clicking an actionButton(), before calculating an expression or taking an action. [...] Use observeEvent whenever you want to perform an action in response to an event.

Therefore, observeEvent needs the user to make an action to run the function inside. In your example, input "a" will be updated only if the user manually changes input "b" and vice-versa. That's why observeEvent works in this scenario and observe does not.

Edit: to have clearer results of the loop created when using observe , here's a slightly modified version of the example in the OP:

library(shiny)

shinyApp(
  ui = fluidPage(
    numericInput('a','a', value = NULL, max = 1, min = 0),
    numericInput('b','b', value = NULL, max = 1, min = 0)
  ),
  server = function(input, output, session) {

    # Using a single observer ----
    observe({

      updateNumericInput(session, 'a', value = 1-req(input$b))

      updateNumericInput(session, 'b', value = 1-req(input$a))

      print(paste0("a = ", input$a))
      print(paste0("b = ", input$b))

    })


    # Using separate observers ----
    # observeEvent(input$b, {
    #   updateNumericInput(session, 'a', value = 1-input$b)
    # })
    # 
    # observeEvent(input$a, {
    #   updateNumericInput(session, 'b', value = 1-input$a)
    # })

  }
)

Careful with the interpretation: the beginning values of a and b are printed. Then, the new values of a and b are created but the new value of b depends on the first value of a (not on the second value of a ). The third values of a and b will depend on the second values of a and b , etc.

In first case the observer looks at all its dependencies, here input$a and input$b . If anything changes it fires its code. Now, when a or b changes the observer updates numericInputs. But it notifies the observer that its dependencies changed and it has to run again. That's why you end up with an infinite loop.

When you use observeEvent s it's a little bit different. Suppose you set a = 1 . One of the observers is triggered and it updates b , so b = 0 . Now, the other observer is triggered and it updates a , so a = 1 . But in this case, the new value is exactly the same as the old one, so internal mechanisms of reactivity in shiny stops it from invalidating a again since it would be redundant.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM