observeEvent and eventReactive with multiple reactive "triggers" in eventExpr

18,482 views
Skip to first unread message

Enzo

unread,
Apr 21, 2017, 11:28:22 AM4/21/17
to Shiny - Web Framework for R
I really like some of the features of observeEvent and eventReactive, like the ability to set autodestroy; ignoreNull; ignoreInit etc.

But as an old shiny hand more familiar to reactive and observe, I always have some doubt on what is the best practice with multiple reactive values.

Shiny reference 1.02 says for eventExpr:
A (quoted or unquoted) expression that represents the event; this can be a simple reactive value like input$click, a call to a reactive expression like dataset(), or even a complex expression inside curly braces


Let's imagine to have input$a (numeric) and input$b (char) as triggering reactive values.  

What is the best practice to structure my eventExpr for  themes common conditions like an OR of all reactive values, or an AND of reactive values.

Thanks Enzo

3D0G

unread,
Apr 21, 2017, 3:26:05 PM4/21/17
to Shiny - Web Framework for R
Enzo - Use a vector for all the inputs/reactives you want to trigger the observeEvent (inputs trigger when their value changes). Then do your observeEvent( c(trigger1, trigger2, trigger3...), { code }). Do any ANDs and ORs in the code section. You probably want to AND and OR on the *value* of the trigger anyhow, not on whether it has changed. I don't know of any easy way to AND or OR on whether a trigger value has *changed*; I believe that requires multiple reactives - Tom

Bárbara Borges

unread,
Apr 21, 2017, 5:10:54 PM4/21/17
to Shiny - Web Framework for R
I agree with the answer above. Also, while it's possible to have arbitrary complex `eventExpr` expressions, it doesn't really do much for you (the observer is still always triggered whenever a reactive in that arbitrary expression changes, the only difference is that the handlerExpr portion can be skipped if eventExpr returns directly). There is no advantage to this rather than doing this check at the very start of you handlerExpr and this latter approach has the advantage of better readability IMO.

Here's a few examples to make clear what I mean:

If you want to create an eventReactive/observeEvent for multiple inputs, you just declare them all:

library(shiny)

ui <- fluidPage(
  numericInput("a", "a", 0),
  textInput("b", "b")
)

server <- function(input, output, session) {
  observeEvent({
    input$a
    input$b
  },{
    print(sprintf(
      "Either 'a' or 'b' changed (a = %s; b = '%s')", 
      input$a, input$b
    ))
  })
}

shinyApp(ui, server)

i.e. the example above is exactly the same as:

library(shiny)

ui <- fluidPage(
  numericInput("a", "a", 0),
  textInput("b", "b")
)

server <- function(input, output, session) {
  observe({
    print(sprintf(
      "Either 'a' or 'b' changed (a = %s; b = '%s')", 
      input$a, input$b
    ))
  })
}

shinyApp(ui, server)

But, if you want to, you can create a more complicated eventExpr. For example, let's say that you only want to run the handlerExpr if `a` is greater than 10 and (in the sense of  `&&`) `b` is "truthy." Then, you can write something like (notice that you have to return if the condition you don't want isn't met):

library(shiny)

ui <- fluidPage(
  numericInput("a", "a", 0),
  textInput("b", "b")
)

server <- function(input, output, session) {
  observeEvent({
    if ((input$a > 10) && isTruthy(input$b)) TRUE
    else return()
  },{
    print(sprintf(
      "'a' is greater than 10 and is 'b' is truthy (a = %s; b = '%s')", 
      input$a, input$b
    ))
  })
}

shinyApp(ui, server)

But, as was pointed out above, I'd recommend keeping all of this logic in the handlerExpr since that allows you to have less repetition. Just include the check directly in handlerExpr and you don't have to remember to return like you had before (here I'm also using a neat little helper that makes the observer depend on all app inputs -- reactiveValuesToList(input)):

library(shiny)

ui <- fluidPage(
  numericInput("a", "a", 0),
  textInput("b", "b")
)

server <- function(input, output, session) {
  observeEvent(reactiveValuesToList(input), {
    if ((input$a > 10) && isTruthy(input$b)) {
      print(sprintf(
        "'a' is greater than 10 and is 'b' is truthy (a = %s; b = '%s')", 
        input$a, input$b
      ))
    }
  })
}

shinyApp(ui, server)


Enzo

unread,
Apr 22, 2017, 11:36:04 AM4/22/17
to Shiny - Web Framework for R
Barbara & Tom this is fantastic stuff and exaclty what I was looking for.

Dean Attali

unread,
Apr 22, 2017, 6:52:49 PM4/22/17
to Shiny - Web Framework for R
Barbara - this post will likely be a great resource for people in the future when they look for how to use observeEvent with complex expressions. Very useful.

Andrew Sali

unread,
Apr 23, 2017, 3:10:34 AM4/23/17
to Shiny - Web Framework for R
I would maybe add to Barbara's examples that in my opinion it is usually a good idea to override the default value of ignoreNULL when having multiple reactive dependencies in the event expression of observeEvent. Otherwise a NULL return value can accidentally annull the effect of the other inputs as well, which is usually not the desired outcome.

As an example if you add an actionButton to Barbara's example as the last element of the event expression, the handler will not execute at all until the first time the button is pressed, since the return value of the event expression is the button value which is NULL until pressed at least once:

library(shiny)


ui
<- fluidPage(
  numericInput
("a", "a", 0),

  textInput
("b", "b"),
  actionButton
("c","c")

)


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

    input$a
    input$b
    input$c
 
},{
   
print(sprintf(
     
"Either 'a' or 'b' or 'c' changed (a = %s; b = '%s', c=%s)",
      input$a
, input$b, input$c
   
))
 
})
}


shinyApp
(ui, server)

Adding ignoreNULL = FALSE to the above observeEvent gives the more intuitive outcome of firing on all 3 inputs when changed.

Cheers,

Andras

Andrew Sali

unread,
Apr 23, 2017, 4:14:44 AM4/23/17
to Shiny - Web Framework for R
I was a bit imprecise in my previous post as the initial value of the action button is not NULL but 0, however it is treated as NULL in observeEvent via `shiny:::isNullEvent`

The main point I wanted to articulate however is that if you have multiple reactives in the event expression and you don't take care of the return value explicitly, it can happen that you accidentally return a NULL (or something that observeEvent considers a NULL event) and that can be frustrating to debug as the observeEvent is not firing. So that's why I find it often helpful to set ignoreNULL=FALSE when aiming to depend on multiple reactives / reactiveValues in observeEvent.

Cheers,

Andras

Enzo

unread,
Apr 23, 2017, 11:53:39 AM4/23/17
to Shiny - Web Framework for R
If I play with Andrew's code, which basically adds an`action button` to the mix, I get a sort of odd result.

It sounds right as Andrew implies to add `ignoreNULL = FALSE` to the code.  Without it, at the beginning the input$a reactive doesn't fire (or, better, it does, but it is filtered out by the `action button`?), so I miss the first input$a pre-selected value.

But now, somehow something else does not seem right:
  • The pre-selected input$a reactive fires and therefore the `printf` is printed on the console
  • I click on the action button and NOTHING happens (the first time)
  • I click the second time and the action button fires the reactive and everything goes to normal
I haven't tried to put on the reactive trace, but it seems as if the first execution of `observeEvent` is missing the first action button click.

Is this an expected behaviour?



library(shiny)

ui
<- fluidPage(fluidRow(column( 4,

  numericInput
("a", "a", 0),

  textInput
("b", "b")

 
, actionButton("c","c")

),
column
(8, textOutput("out1"))
))





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


    input$a
    input$b
    input$c
 
},{
   
print(sprintf(
     
"Either 'a' or 'b' or 'c' changed (a = %s; b = '%s', c=%s)",
      input$a
, input$b , input$c
   
))

 
}, ignoreNULL = FALSE)
}




shinyApp
(ui, server)


On Friday, April 21, 2017 at 4:28:22 PM UTC+1, Enzo wrote:

Andras Sali

unread,
Apr 23, 2017, 1:18:41 PM4/23/17
to Enzo, Shiny - Web Framework for R
Hi Enzo,

I am not sure I am able to reproduce the behaviour you describe. If I run the code I posted with ignoreNULL = FALSE, then the reactive fires when the app starts with c=0 and when I click the action button it fires again with c=1, producing alltogether the following two lines:

[1] "Either 'a' or 'b' or 'c' changed (a = 0; b = '', c=0)"
[1] "Either 'a' or 'b' or 'c' changed (a = 0; b = '', c=1)"

So all looks as expected. Just to summarize:

  • With ignoreNULL=TRUE (default setting), the reactive does not fire even if `a` and `b` are repeatedly changed, until the first time the actionButton is clicked (thus it does not fire either on app load). Once the button is clicked, the reactive correctly updates for each change in `a` and `b` and also in `c`.
  • With ignoreNULL=FALSE the reactive always updates for each change in `a`, `b` and `c` and thus also runs on app start.
Since the first case is not so intuitive in my opinion (especially if instead of an actionButton you have some complicated reactive), I think it's a best practice to set ignoreNULL=FALSE for multiple reactive dependencies in observeEvent - YMMV.

Cheers,

Andras

--
You received this message because you are subscribed to a topic in the Google Groups "Shiny - Web Framework for R" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/shiny-discuss/vd_nB-BH8sw/unsubscribe.
To unsubscribe from this group and all its topics, send an email to shiny-discus...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/shiny-discuss/1b5ef89c-1b28-4f34-933c-14e886964ec7%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Enzo

unread,
Apr 23, 2017, 1:26:54 PM4/23/17
to Shiny - Web Framework for R
Andras

I get the first line `[1] "Either 'a' or 'b' or 'c' changed (a = 0; b = '', c=0)"`

Then I click on the action button (first time)  once and I do NOT get anything.

I click on the action button one more time and I get what I as expecting the first time:

[1] "Either 'a' or 'b' or 'c' changed (a = 0; b = '', c=1)"



It seems I'm missing a click.   I would be grateful if other people could try as well.

(latest everything; OSX; Safari; R cl)

Enzo

On Friday, April 21, 2017 at 4:28:22 PM UTC+1, Enzo wrote:

Bárbara Borges

unread,
Apr 24, 2017, 4:07:39 PM4/24/17
to Shiny - Web Framework for R
Enzo,

I cannot reproduce that behavior either... I'm also on Mac and Safari. I get the same behavior that Andras described...

I also agree with Andras that setting `ignoreNULL = FALSE` is probably best if you're using multiple reactives in the eventExpr.

Enzo, you can try starting with the easiest possible exmaple:

library(shiny)
ui <- fluidPage(actionButton("c","c"))

server <- function(input, output, session) {
  observeEvent({ input$c },{
    print(sprintf("'c' changed (c = %s)", input$c))
  }, ignoreNULL = FALSE)
}

shinyApp(ui, server)

And gradually changing it to see where your problem occurs. Make sure to reinstall Shiny and start with a clean session, so that we can rule out any weirdness...

3D0G

unread,
Apr 25, 2017, 12:50:53 PM4/25/17
to Shiny - Web Framework for R
This is a really helpful discussion.

Andrew's suggestion implies that an observeEvent will not execute if any one of the multiple inputs is null. Playing around with the code demonstrates this is correct. I use actionButton/observeEvent a lot and usually don't want execution before the button is pressed, so it's good to know that it will work that way with multiple triggers by default.

Enzo, I cannot reproduce what you're seeing.

Also, in my original answer I suggested putting the multiple inputs in a vector; that might be the source of some problems.

If you wrap the inputs that are inside the print statement with class(), you'll see these inputs are all different classes. That means either a vector won't work, or R will modify the inputs so they all have the same class, which could cause problems.

In Barbara's example she just puts each trigger on its own line inside brackets. This makes for unusual-looking R code, but Shiny seems full of that kind of surprise. It also seems to work to put them, inside brackets, all on one line separated with semicolons. Or put them in a list.

Tom



 

Andrew Sali

unread,
Apr 25, 2017, 1:35:29 PM4/25/17
to Shiny - Web Framework for R
Hi Tom,

Let me clarify this a little bit as I think there are still might be some confusions. The way to think about the event expression is that Shiny creates a function with the body given by the expression. If you think of Barbara's code as being in a function, it's not strange at all. This also implies that the last statement is by R standards the return value of the function.

So in my example, if I would rearrange the order of the inputs in the event expression, the behaviour would be different as I am not returning anymore the actionButton value. If you run the code below (notice I moved input$c up), it will behave as if ignoreNULL=FALSE is set in my original example, as the return value by default is now an empty string and not an unclicked actionButton. So what really matters with ignoreNULL=TRUE (default setting) is whether the last statement you have in the event expression is NULL / unclicked actionButton (or if you explicitly give a return value then same applies to that). For example, if you return list(input$a,input$b,input$c) it will still fire on app start as the return list is not NULL.




library
(shiny)


ui
<- fluidPage(

  numericInput
("a", "a", 0),
  textInput
("b", "b"),
  actionButton
("c","c")
)





server
<- function(input, output, session) {
  observeEvent
({
    input$a
    input$c
    input$b
 
},{

   
print(sprintf(
     
"Either 'a' or 'b' or 'c' changed (a = %s; b = '%s', c=%s)",

      input$a
, input$b, input$c
   
))
 
})
}




shinyApp
(ui, server)


Hope this clarifies it a bit. So the takeaways in my opinion are:
  • Think of the event expression as the body of the function and check what the return value is. 
  • observeEvent will create a dependency on all inputs you have in the event expression
  • However if your return value just discussed is NULL / unclicked actionButton the handler expression will not run by default (hence my suggestion to override ingoreNULL)

Cheers,

Andras

3D0G

unread,
Apr 25, 2017, 1:55:02 PM4/25/17
to Shiny - Web Framework for R
Wow. Thanks for the insight on how this really works! - Tom

Joe Cheng

unread,
Apr 25, 2017, 6:17:50 PM4/25/17
to 3D0G, Shiny - Web Framework for R
Great discussion. This pseudocode of observeEvent may help. (Think of exprToFunction as just a way to turn caller-provided expressions into functions, so we can call/execute them multiple times, rather than just have them evaluated once.)

observeEvent <- function(eventExpr, handlerExpr, ignoreNULL=TRUE) {
  eventFunc <- exprToFunction(eventExpr)
  handlerFunc <- exprToFunction(handlerExpr)

  observe({
    e <- eventFunc()
    if (ignoreNULL && isNullEvent(e)) {
      return()
    }
    isolate(handlerFunc())
  })
}

The actual implementation in shiny is slightly more complicated due to some additional options and debugging aids, but this accurately portrays the relationship between ignoreNULL and the value of eventExpr.

If you want a simple way of combining multiple inputs in an AND relationship, you can use req():

library(shiny)

ui <- fluidPage(
  numericInput("a", "a", 0),
  textInput("b", "b"),
  actionButton("c","c")
)

server <- function(input, output, session) {
  observeEvent(req(input$a, input$b, input$c), {
    print(sprintf(
      "'a', 'b', and 'c' all changed (a = %s; b = '%s', c=%s)",
      
      input$a, input$b, input$c
    ))
  })
}

shinyApp(ui, server)

However, note that req() uses a different, looser definition for what a "falsy" value is than observeEvent's default behavior (isNullEvent). For example, FALSE and 0 (of a non-action-button variety) are both considered falsy values according to req(), but observeEvent doesn't consider them to be NULL. When you use a req in an observeEvent like this, it's req()'s definition that is more relevant, as it will abort the execution of the observer as soon as it encounters a falsy value.

On Tue, Apr 25, 2017 at 10:55 AM 3D0G <tom.we...@gmail.com> wrote:
Wow. Thanks for the insight on how this really works! - Tom

--
You received this message because you are subscribed to the Google Groups "Shiny - Web Framework for R" group.
To unsubscribe from this group and stop receiving emails from it, send an email to shiny-discus...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/shiny-discuss/27d7854d-0c07-482c-9a82-5729e942fa41%40googlegroups.com.

Enzo

unread,
Apr 26, 2017, 5:06:52 AM4/26/17
to Shiny - Web Framework for R
Superuseful clarification Joe. Thanks!


On Friday, April 21, 2017 at 4:28:22 PM UTC+1, Enzo wrote:

boec...@gmail.com

unread,
Mar 27, 2018, 9:22:01 AM3/27/18
to Shiny - Web Framework for R
I'm having a very similar problem that I've outlined here:

If anyone could help me shed any light on this issue that would be amazing.


On Friday, April 21, 2017 at 4:28:22 PM UTC+1, Enzo wrote:
Reply all
Reply to author
Forward
0 new messages