Using Observers in Shiny for Conditional Logic Without if() Statements

Use if statement in many reactive objects doesn’t work

The question of the day is: “How can I use an if statement to conditionally create different outputs based on the value of a reactive object?” Well, let’s dive into the world of reactive programming and explore this issue.

Understanding Reactive Objects

In R Shiny, a reactive object is an object that depends on one or more input variables. When the input variable(s) change, the reactive object recalculates its value using its reactive function. This allows us to create dynamic output based on user interactions.

In our example code, we have several reactive objects: currentvariable0, currentvariable1, …, currentvariable5. These objects depend on the selected input variable(s) and recalculate their values when the user selects a new option.

The Problem

The problem arises when we try to use an if statement within these reactive objects. Specifically, we want to create different outputs based on the value of currentvariable5. However, Shiny doesn’t allow us to use traditional if statements in this way.

When we add a simple if statement, like if(currentvariable5=="A"), we encounter an error message indicating that Operation not allowed without an active reactive context.

The Solution: Using observe

To overcome this limitation, we can use the observe function to wrap our conditional logic. The observe function allows us to execute a piece of code when the output of another reactive object changes.

In our example, we wrap the entire logic inside an observe function:

observe({
  if(currentvariable5=="A") {
    # create output for A
  } else {
    # create output for B
  }
})

By doing so, we ensure that the code inside the observe function is executed only when the value of currentvariable5 changes.

The Benefits

Using observe provides several benefits:

  1. Flexibility: We can now use traditional if statements to conditionally create different outputs.
  2. Efficiency: The observe function ensures that the code inside it is executed only when necessary, reducing unnecessary computations.
  3. Readability: By wrapping our logic in an observe function, we make it clearer what’s happening under the hood.

Example Code

Here’s the modified example code with the observe function:

library(leaflet)
library(leaflet.providers)
library(ggplot2)
library(shinythemes)
library(sf)
library(lubridate)
library(dplyr)
library(rgdal)
library(rgeos)

# get AOI
download.file(
  "https://github.com/Leprechault/trash/raw/main/stands_example.zip",
  zip_path <- tempfile(fileext = ".zip")
)
unzip(zip_path, exdir = tempdir())

# Open the files
setwd(tempdir())
stands_extent <- readOGR(".", "stands_target") # Border
stands_ds <- read.csv("pred_target_stands.csv", sep=";") # Data set
stands_ds <- stands_ds %>%
  mutate(DATA_S2 = ymd(DATA_S2))
stands_ds$CLASS<-c(rep("A",129),rep("B",130)) 
stands_ds$CD<-abs(rnorm(length(stands_ds[,1]),mean=50))

# Create the shiny dash
ui <- fluidPage(
  theme = shinytheme("cosmo"),
  titlePanel(title="My Map Dashboard"),  
  sidebarLayout(
    sidebarPanel(
      selectInput(inputId = "selectedvariable0",
                  label = "Type", 
                  choices = c(unique(stands_ds$PEST)),selected = TRUE ), 
      selectInput(inputId = "selectedvariable1",
                  label = "Date", 
                  choices = c(unique(stands_ds$DATA_S)),selected = TRUE ), 
      selectInput(inputId = "selectedvariable2",
                  label = "Project", 
                  choices = c(unique(stands_ds$PROJETO)),selected = TRUE ),
      selectInput(inputId = "selectedvariable3",
                  label = "Stand", 
                  choices = c(unique(stands_ds$CD_TALHAO)),selected = TRUE),
      selectInput(inputId = "selectedvariable4",
                  label = "Unique ID", 
                  choices = c(unique(stands_ds$ID_UNIQUE)),selected = TRUE),
      selectInput(inputId = "selectedvariable5",
                  label = "Class", 
                  choices = c(unique(stands_ds$CLASS)),selected = TRUE)            
    ),
    mainPanel(
      textOutput("idSaida"),
      fluidRow(
        splitLayout(plotOutput("myplot"))),
      dateInput(inputId = "Dates selection", label = "Time"),
      leafletOutput("map") 
    )
  )
)
server <- function(input, output){
    
    currentvariable0 <- reactive({input$selectedvariable0})
    currentvariable1 <- reactive({input$selectedvariable1})
    currentvariable2 <- reactive({input$selectedvariable2})
    currentvariable3 <- reactive({input$selectedvariable3})
    currentvariable4 <- reactive({input$selectedvariable4})
    currentvariable5 <- reactive({input$selectedvariable5})
    
    observe({
      if(currentvariable5=="A"){
        
        output$myplot <- renderPlot({
            
            #Subset stand
            stands_sel <- subset(stands_extent, stands_extent@data$ID_UNIQUE==currentvariable4())
            
            #Subset for input$var and assign this subset to new object, "fbar"
            ds_sel<- stands_ds[stands_ds$ID_UNIQUE==currentvariable4(),]
            
            #Create a map
            polys <- st_as_sf(stands_sel)
            ggplot() +
              geom_sf(data=polys) +
              geom_point(data=ds_sel,
                         aes(x=X, y=Y), color="red") +
              xlab("Longitude") + ylab("Latitude") +
              coord_sf() +
              theme_bw() +
              theme(text = element_text(size=10)) 
        })
      } else {
        
        #Subset stand
        stands_sel <- subset(stands_extent, stands_extent@data$ID_UNIQUE==currentvariable4())
        
        #Subset for input$var and assign this subset to new object, "fbar"
        ds_sel<- stands_ds[stands_ds$ID.Unique==currentvariable4(),]
        
        #Create a map
        polys <- st_as_sf(stands_sel)
        ggplot() +
          geom_sf(data=polys) +
          geom_raster(data = stands_sel, aes(x = X, y = Y, fill = CD)) + 
          scale_fill_gradientn(name="Desfolha (%)",colours = terrain.colors(100))+
          xlab("Longitude") + ylab("Latitude") +
          coord_sf() +
          theme_bw() +
          theme(text = element_text(size=10)) 
        
      }
      
      output$map <- renderLeaflet({
          
          stands_actual<-stands_ds[stands_ds$ID_UNIQUE==currentvariable4(),]
          lng <- mean(stands_actual$X)
          lat <- mean(stands_actual$Y)
          
          leaflet() %>%
            setView(lng = lng, lat = lat, zoom=17) %>%
            addProviderTiles(providers$Esri.WorldImagery) %>%                   
            addMarkers(lng=stands_actual$X, lat=stands_actual$Y, popup="Location")
          
      })}) #end of observe function.
}
shinyApp(ui, server)

By using the observe function, we can now conditionally create different outputs based on the value of currentvariable5.

Conclusion

In conclusion, when working with reactive objects in Shiny, it’s essential to understand how to use the observe function to wrap conditional logic. By doing so, we can create dynamic output that responds to changes in our input variables.

Remember: always keep your code organized and readable by using meaningful variable names and functions. Happy coding!


Last modified on 2023-07-15