Search code examples
rshinyinvalidation

increment reactivevalue inside invalidatelater observer


I'm trying to build shinyapp to make a very simple quizz. I have a data.frame with 10 questions, and the user clicks to answer either 0 or 1. It worked until I tried to implement a timer / countdown so that the next question appears automatically after 5 seconds, using an invalidateLater() call.

The current question number is stored in a reactiveValues() object, variable i

When I add my observe function, the app stopped working and did not display the question. My code is here:

init = data.frame(question=paste("question", 1:10), correct=c(1,1,1,1,1,0,0,0,0,0), answer=NA, stringsAsFactors = FALSE)
library(shiny); library(shinydashboard)
ui=dashboardPage(dashboardHeader(title = "questionnaire"),dashboardSidebar(),dashboardBody(
        fluidRow(box(width = 6, title="how it works..", p("balblabla"))),
        fluidRow(
            box(width = 12, background = "orange",
          fluidRow(
            box(width = 12, 
                verbatimTextOutput("question"),
                tags$head(tags$style("#question{font-size: 20px; text-align: left; font-weight: bold;}"))
            ),
            box(width = 12,
                actionButton("negative", "answer 0", width = '30%'),
                actionButton("positive", "answer 1", width = '30%')
            ))))))
server <- function(input, output, session) {
  vals = reactiveValues(df = init, i=1)
  output$question <- renderText({vals$df[vals$i, "question"]})
  observe({
    invalidateLater(5000)
    vals$i <- vals$i + 1
  })
  observeEvent(input$negative, ignoreInit=TRUE, {
    vals$df[vals$i, "answer"] = 1
    if (vals$df[vals$i, "correct"]==1){
      showNotification("WRONG!", type="error", duration=1, closeButton = FALSE)
    } else {
      showNotification("CORRECT!", type="message", duration=1, closeButton = FALSE)
    }})
  observeEvent(input$positive, ignoreInit=TRUE, {
    vals$df[vals$i, "answer"] = 1
    if (vals$df[vals$i, "correct"]==0){
      showNotification("WRONG!", type="error", duration=1, closeButton = FALSE)
    } else {
      showNotification("CORRECT!", type="message", duration=1, closeButton = FALSE)
    }})
}
shinyApp(ui, server)

Can you help me fix this observe statement, i Don't know what's going wrong here.


Solution

  • You need to isolate the increment of the index within this observer:

      observe({
        invalidateLater(5000)
        vals$i <- vals$i + 1
      })
    

    , otherwise you'll send it in an infinite loop because a value that it "observes" is changed within the observer itself, constantly triggering re-evaluation irrespective from elapsed time (you can verify this by adding a print(vals$i) instruction in your current code, at the end of the observer.

    So, something like this seems to work:

      vals = reactiveValues(df = init, i=0)
      output$question <- renderText({vals$df[vals$i, "question"]})
      observe({
        invalidateLater(5000)
        isolate(vals$i <- vals$i + 1)
      })
    

    Note that I changed the initialization of i to zero to avoid skipping a question in initialization.

    HTH.