Advantages of reactive vs. observe vs. observeEvent

agf1997 picture agf1997 · Oct 26, 2018 · Viewed 16k times · Source

I've reading everything I can about shiny reactive programming. I'm a bit confused. The following all work but what is the preferred method and why? Obviously the example below is simple but will I run into trouble when creating a larger application with any of the methods?

I've been tending to gravitate towards the style in server code #1. Reason being, is that I'm able to break up the if statements. To me this seems much more readable. Again, the simple examples below aren't terribly complex but you can easily imagine how server code 2 and server code 3 could get very confusing with lots of nested if / if else statements.

UI Code

library(shiny)

ui <- fluidPage(
  selectInput(inputId = 'choice',
              label = 'Choice',
              choice = c('Hello','Goodbye'),
              selected = c('Hello')
  ),

  textOutput('result')

)

Server Code 1

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

  text <- reactiveValues()

  observe({
    if (input$choice == 'Hello') {
      text$result <- 'Hi there'
      }
    })

  observe({
    if (input$choice == 'Goodbye') {
      text$result <- 'See you later'
      }
    })

  output$result <- renderText({
    text$result
  })

})

shinyApp(ui = ui, server = server)

Server Code 2

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

  getStatus <- reactive({

    if (input$choice == 'Hello') {
      'Hi there'
    } else if (input$choice == 'Goodbye'){
      'See you later'
    }
  })

  output$result <- renderText({
    getStatus()
  })

})

shinyApp(ui = ui, server = server)

Server Code 3

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

  text <- reactiveValues()

  observeEvent(input$choice,{
    if (input$choice == 'Hello') {
      text$result <- 'Hi there'
    } else if (input$choice == 'Goodbye') {
      text$result <- 'See you later'
    }
  })

  output$result <- renderText({
    text$result
  })

})

shinyApp(ui = ui, server = server)

Answer

Chabo picture Chabo · Oct 27, 2018

First off this stuff is sort of ambiguous, and not very intuitive in some ways, it even says so on the Shiny blog!

Here is my best understanding of the topic..

Lets start with reactive

The reactive function allows a user to monitor the status of an input or other changing variable and return the value to be used elsewhere in the code. The monitoring of a reactive variable is considered lazy, "Reactive expressions use lazy evaluation; that is, when their dependencies change, they don't re-execute right away but rather wait until they are called by someone else.(Source)". You show this well in example 2, as you can call the variable inside the renderText environment, once called the code inside the reactive call executes and re-evaluates the variable.

For science nerds, this is a lot like quantum mechanics in that by calling the reactive variable (observing it) causes it to change by re-evaluating, too much of a stretch?

Now to observe

Observe is similar reactive, the main difference is that it does not return any values to any other environment besides its own, and it is not lazy. The observe function continually monitors any changes in all reactive values within its environment and runs the code in it's environment when these values are changed. So, observe is not a "lazy" evaluation since it does not wait to be called before it re-evaluates. Again note that you cannot assign variables from observe.

For sake of experiment:

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

   observe({
   if (input$choice == 'Hello') {
      getStatus <- 'Hi there'
    }
  })

  observe({
    if (input$choice == 'Goodbye') {
      getStatus <- 'See you later'
    }
  })

  output$result <- renderText({
    getStatus
  })

})

shinyApp(ui = ui, server = server)

enter image description here

What is important to notice is that during the code executed in observe, we can manipulate outside environment reactive variables. In your case you assign text <- reactiveValues() and then manipulate that by calling text$result <- 'Hi there'. We can also do things like update selectInput choices, or other shiny widgets, but we cannot assign any non-reactive variables in this environment like our getStatus in the example above. And this idea is mentioned on the observe documentation,

"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. But unlike reactive expressions, it doesn't yield a result and can't be used as an input to other reactive expressions. Thus, observers are only useful for their side effects (for example, performing I/O)(Source)"

Lastly, observeEvent

The best way to use observeEvent is to think of it as a defined trigger, as in it watches one event or change in a variable, and then fires when the event happens. I most commonly use this to watch input to buttons, as that is a defined event in which I want things to happen after the button is pushed. It uses an isolate environment which I think is the perfect name for how this function works.

Inside this environment we can call a bunch of reactive variables, but we only define one as the trigger. The main difference between observeEvent and observe being the trigger, as observe runs anytime anything changes, and observeEvent waits for the trigger. Note that this environment is similar to observe in that it does not return non-reactive variables.

Summary

Here is an example that brings all these ideas together:

library(shiny)

ui<-
 fluidPage(
   fluidRow(
     column(4,
      h2("Reactive Test"),
      textInput("Test_R","Test_R"),
      textInput("Test_R2","Test_R2"),
      textInput("Test_R3","Test_R3"),
      tableOutput("React_Out")
    ),
     column(4,
      h2("Observe Test"),
      textInput("Test","Test"),
      textInput("Test2","Test2"),
      textInput("Test3","Test3"),
      tableOutput("Observe_Out")
    ),
    column(4,
      h2("Observe Event Test"),
      textInput("Test_OE","Test_OE"),
      textInput("Test_OE2","Test_OE2"),
      textInput("Test_OE3","Test_OE3"),
      tableOutput("Observe_Out_E"),
      actionButton("Go","Test")
    )

    ),
  fluidRow(
    column(8,
    h4("Note that observe and reactive work very much the same on the surface,
       it is when we get into the server where we see the differences, and how those
       can be exploited for diffrent uses.")
  ))

  )

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

# Create a reactive Evironment. Note that we can call the varaible outside same place
# where it was created by calling Reactive_Var(). When the varaible is called by
# renderTable is when it is evaluated. No real diffrence on the surface, all in the server.

Reactive_Var<-reactive({c(input$Test_R, input$Test_R2, input$Test_R3)})

output$React_Out<-renderTable({
  Reactive_Var()
  })

# Create an observe Evironment. Note that we cannot access the created "df" outside 
# of the env. A, B,and C will update with any input into any of the three Text Feilds.
observe({
  A<-input$Test
  B<-input$Test2
  C<-input$Test3
  df<-c(A,B,C)
  output$Observe_Out<-renderTable({df})
  })

#We can change any input as much as we want, but the code wont run until the trigger
# input$Go is pressed.
observeEvent(input$Go, {
  A<-input$Test_OE
  B<-input$Test_OE2
  C<-input$Test_OE3
  df<-c(A,B,C)
  output$Observe_Out_E<-renderTable({df})
})

}
shinyApp(ui, server)

reactive Create a variable that can be changed over time by user inputs, evaluates "lazy" meaning only when called.

observe Continually monitor reactive events and variables, whenever ANY reactive variable is changed in the environment (the observed environment), the code is evaluated. Can change values of previously defined reactive variables, cannot create/return variables.

observeEvent (Domino Effect) Continually monitor ONE defined reactive variable/event (the trigger) and run the code when the the trigger is activated by change/input of that trigger. Can change values of previously defined reactive variables, cannot create/return variables.

eventReactive Create a variable, with a defined trigger similar to observeEvent. Use this when you want a reactive variable that evaluates due to a trigger instead of when it is called.

I hope this helps, and if I am mistaken in my understanding or there could be more clarification, feel free to edit this answer.