rshinytidyversereprex

Shiny observeEvent shows first event, but not second, third


TL;DR: observeEvent is only working on the first instance, but not subsequent.

Explanation: In a small shiny app below, I dynamically build a URL based on user input, which points to the corresponding pre-rendered "timer" gif images I have hosted on GH. In this shiny app code below, observeEvent works great to pull the sliderInput value (e.g. 5sec), build a URL, and then clicking 'go' will show the timer using a clever shinyjs package. However, if I do not change the input (still 5sec), clicking go doesn't rebuild the image. If I change the value to a different number (4sec), it will show the correct gif. If I change back to the original number (5sec), no gif. If I go to a new number (3sec), correct gif. If I print the value of input$time or of rv$time in my observeEvent, then each of those values are updating correctly (both print the corresponding value).

Goal: to show the gif that corresponds to the input$time upon each update of input$go

Reprex:

library(shiny)
library(tidyverse)
library(glue)
library(shinyjs)

# Define UI
ui <-  navbarPage(title = "Reprex", 
                  ## RHYME TIME -----
                  tabPanel("Time",
                           useShinyjs(),
                           sidebarLayout(
                             sidebarPanel( 
                               sliderInput("time",
                                           "Seconds",
                                           min = 3,
                                           max = 10,
                                           value = 5
                               ),
                               actionButton(inputId = "go",
                                            label = "Go"), 
                             ),
                             mainPanel(
                               HTML("<center>"),
                               shinyjs::hidden(htmlOutput("simple_timer")),
                               HTML("</center>")
                               
                             )
                           )
                  )
)

# Define server logic
server <- function(input, output, session) {
  #a container to store my time var
  rv <- reactiveValues(
    time = 0
  )
  
  #the event that is triggered by input$go getting clicked
  observeEvent(input$go, {
    rv$time <- input$time #this should update rv$time after go is clicked
    shinyjs::show("simple_timer") #this is the clever package with simple show/hide funs
  })
  
  #the reactive text that generates my HTML
  output$simple_timer <- renderText({
    glue::glue('<img src ="https://github.com/matthewhirschey/time_timer/raw/main/data/{rv$time}_sec.gif", 
            align = "center", 
            height="50%", 
            width="50%">')
  })
}

# Run the application 
shinyApp(ui = ui, server = server)

Solution

  • There are a couple of issues with your code:

    1. renderText won't refire if you press input$go again (w/o changing the slider). Becasue the idea is that observer/render fires whenever their reactives change. As your renderText depends on rv$time which does not change when input$time does not change, the render function is not fired on subsequent button presses. This can be remedied by including input$go in the render function setting an additional dependency on the button.
    2. This will not, however, solve your problem, because the browser uses caching. It sees that the <img> tag did not change (same src), thus it does not reload the picture. To circumvent that you can use the trick from Disable cache for some images by adding a timestamp to the src.

    To make a long story short, this code does the trick:

    output$simple_timer <- renderText({
       input$go # make code dependent on the button
       # add `?timestamp=<timestamp>`to the src URL to convince the browser to reload the pic
       glue::glue('<img src ="https://github.com/matthewhirschey/time_timer/raw/main/data/{rv$time}_sec.gif?timestamp={Sys.time()}", 
                  align = "center", 
                  height="50%", 
                  width="50%">')
    })