Race between observeEvent and update*Input when action button is rapidly clicked

Viewed 83

Background

I have a shiny application for grading student work. The student's work is shown on screen and various buttons/checkmarks are used by the grader to assign grades. There is then a Next button to go to the next student (saving grades etc). There's also a Previous button to allow graders to review their grading history, thus each press of Next or Previous loads the existing grade for the student work into the buttons/checkmarks.

Problem

Unfortunately there is a race condition between updating the UI and moving to the next or previous student: The observeEvent() for Next for example first checks + updates the current student's grade (determined by the state of the buttons/checkmarks, or alternatively a reactive value) and then loads in the new student's current grades and updates the UI. Rapid presses of the Next button means that messages to update the UI can be delayed, and thus the state of the UI (or any reactives in which grades are stored) become updated based on old information from other students. This then is compared to the current student's grades and overwrites - not a nice situation. As long as the graders go slow enough on the "Next" button everything works fine ofcourse - it's just the speedier clickers that hit the fault.

reprex

The checkbox is filled from the stored_checks as you hit the Next button. If you hit Next in succession quickly enough, you'll get the "check has changed" message to the console, even though the status for that entry has obviously not changed as the checkmark button has not been pressed.

The reason is that updateCheckboxInput results in observeEvent being called on the checkbox, updating vals$current. Under rapid pressing of the Next button this updating gets out of sync (i.e. with a value from an earlier call to updateCheckboxInput) so that the Next button's observeEvent thinks the checkbox state has changed when it hasn't.

Note the 1 second sleep is just to make it easier to hit - essentially simulating slower updates (e.g. in our grading app we're loading up the student HTML or PDF work).

library(shiny)

stored_checks <- rep(c(TRUE, FALSE), 50)

ui <- fluidPage(
    checkboxInput("check", "Check"),
    actionButton("nextitem", "Next")
)

server <- function(session, input, output) {
    vals <- reactiveValues(item=1, current=stored_checks[1])
    observeEvent(input$check, { # update this check
        vals$current <- input$check

        Sys.sleep(1) # simulated slowdown of other observers etc
    }, ignoreInit = TRUE)
    observeEvent(input$nextitem, {
        if (vals$current != stored_checks[vals$item]) {
            cat("check has changed\n")
            stored_checks[vals$item] <- vals$current
        }
        vals$item <- vals$item %% 100 + 1
        vals$current <- stored_checks[vals$item]
        updateCheckboxInput(session, "check", value=vals$current)
    })
}

shinyApp(ui = ui, server = server)

Any ideas most welcome!

0 Answers
Related