rstudio / shiny

Easy interactive web applications with R
https://shiny.posit.co/
Other
5.37k stars 1.86k forks source link

Does `observeEvent` priority value also has an influence on `eventReactive`? #3523

Closed stla closed 3 years ago

stla commented 3 years ago

Hello,

I observed a behavior which is not documented. Consider this app:

library(shiny)

ui <- fluidPage(
  selectInput("slct", NULL, letters[1:3]),
  radioButtons("rad", NULL, c("X", "Y")),
  radioButtons("rad2", NULL, c("K", "L"))
)

server <- function(input, output, session){

  observeEvent(input[["slct"]], {
    freezeReactiveValue(input, "rad")
    updateRadioButtons(session, "rad", choices = sample(LETTERS,2L))
  }, priority = 1)

  observeEvent(input[["rad"]], {
    freezeReactiveValue(input, "rad2")
    updateRadioButtons(session, "rad2", choices = sample(LETTERS,2L))
  }, priority = 1)

  R <- eventReactive(list(input[["slct"]], input[["rad"]], input[["rad2"]]), {
    print("rrrrrrrrrr")
    print(input[["rad"]])
    print(input[["rad2"]])
    "XXXX"
  })

  observe({
    print(R())
    cat("-----------------\n")
  })

}

Then, when one changes the value of the select input, the reactive conductor reacts only one time.

Now, remove priority = 1 (or set priority = 0). Then, when one changes the value of the select input, the reactive conductor reacts two times.

Does that mean that a priority value >0 of an observeEvent imply that this observeEvent is executed before any reactive conductor? Have I the good understanding?

jcheng5 commented 3 years ago

Hi @stla! When you say "Now, remove priority = 1 (or set priority = 0)", do you mean for both observeEvent, or just the first one?

stla commented 3 years ago

Hi @jcheng5 , I tried for both ones.

jcheng5 commented 3 years ago

OK, I see what is happening. Reactive conductors themselves do not react, per se. They know when the upstream values they last depended on have changed, and they notify downstream dependents. But they don't get to decide when to re-execute (note that there is no priority argument for reactive() or eventReactive()). Instead, they execute when their values are read. In this case, the R reactive is only read from one place: the observe() at the end of your server function. So the question is not why R executes twice, but rather, why that last observer executes twice.

What's happening here is that when you have priority = 1 for the two observeEvents, the freezeReactiveValue calls are guaranteed to happen before the observe() (if they are going to happen at all). If you set priority = 0 or let the default priority be used, then observeEvents and observe all have the same priority; in that case, the order in which observers are fired is basically undefined (not really--it is deterministic, but, once a few reactive events go through an even moderately interesting Shiny app, it's very difficult to try to keep track in your head).

In this case, with equal priorities, changing the select causes the order of the observe() and observeEvent(input[["rad"]]) to eventually be flipped. So the observe() runs, which causes R() to execute; then the observeEvent(input[["rad"]]) runs, which causes rad2 to update (via a client round-trip); and in the next flush cycle, observe() runs again.

In the case where priority = 1, the observeEvents always run first, and since both of them call freezeReactiveValue() on one of R's dependencies, R is prevented from running until all relevant client updates are complete. That's a good thing--I try to make it a habit to assign a high priority to observers that exist for the sake of propagating changes to inputs like this.

jcheng5 commented 3 years ago

I'm going to go ahead and close this issue as there's no bug or feature request here, I don't think. But feel free to continue the discussion, and reopen if you disagree.

stla commented 3 years ago

Thanks Joe for the explanations. Yes this is a good thing. I used this technique to solve a double reactivity issue in my app.