Shiny caching bug

,

I have an issue with what appears to be caching in a Shiny app, and I cannot find a workaround. I'm hoping someone else can.

The error occurs in the following code:

  # Display a selected record for editing purposes.
  output$edit_committee <- renderUI(
    {
      # There has to be data, and a row must be selected.
      req(display$Committee)
      req(input$display_committee_rows_selected)
      # The user must be an administrator.
      req(admin())
      if (admin()) {
        # Get the selected row.
        record <- database$Committee[input$display_committee_rows_selected, , drop = FALSE]
        # Display it.
        tags <- editCommitteeTagList(record)
        do.call(tagList, tags)
      } else {
        modalDialog(title = "OOPS!",
                    "Sorry, only users with administrator privilege can edit the committee roster."
        ) |> showModal()
        req(admin())
      }
    }
  )

In the UI, "edit_committee" identifies a uiOutput instance that display a bunch of input controls (text fields and check boxes), while "display_committee" identifies a DTOutput instance that shows the data frame named "display$Committee" (part of a list of data frames). There is another list of data frames containing "database$Committee". The "database" list is tables pulled from an external database; the "display" list has modified versions of those tables adapted for display via DTOutput.

The bug manifests as follows. The user selects a row in the displayed table, edits it (via the input controls in the uiOutput instance) and submits it to be saved in the database. After the save occurs, the app pulls the data from the database and updates database$Committee, then uses that to update display$Committee. Crucially, I have confirmed during debugging that both those updates do occur.

Now the user clicks on a row in the DTOutput instance, and the code above populates the edit controls using that row. If the user clicks on any row other than the one just edited, the edit controls are correctly populated. If the user clicks the row last edited, the edit controls are populated using the previous version of the data, even though database$Committee is confirmed to have been update and the correct (post-update) version of the data is showing in the DTOutput. So apparently the line

record <- database$Committee[input$display_committee_rows_selected, , drop = FALSE]

is accessing a cached version rather than the current version of database$Committee (or at least a cached version of the edited row).

Is there any way to force use of the current version of the row?

Now that you are explicitly mentioning that this only happens if the same row number is selected twice, to me it seems this is a lazy loading issue.

A reactive receiving the same value two times won't be triggered:

You could try tracking this behaviour via reactlog:

Thanks! I'm going to give reactlog a try and see if it reveals anything.

I should note that the bug manifests even if the edited row is not the first row clicked on. So, for instance, I edit row 14, reload the data, click on row 7 and the edit controls are correctly populated with row 7 data (which of course is the same pre-edit and post-edit). Then I click on row 14 and the edit controls are populated with the pre-edit data.

I see - in the end without getting to know the reactive structure it's all guesswork.

I just narrowed things down slightly. I hacked the code to edit not just the row selected by the user but also the row above it. After the changes were pushed to the database, the displayed version of the table showed both edits. As before, selecting the edited row showed pre-edit data. Selecting the row above it also showed pre-edit data. So the problem is not that "record" is a cached value; it's that database$Committee (data frame) is a cached value, even though outside the renderUI call it shows the updated value.

1 Like

You could try to trigger the renderUI call with your delete button to force an update. Just place input$my_delete_button somewhere in the renderUI function or use bindEvent().

I put input$my_delete_button inside the call, but with no visible effect. Hitting the delete button did not seem to change anything. Then I tried piping the renderUI function to bindEvent(input$my_delete_button), and the result was that it never executed, despite my hitting deleted multiple times after clicking on a row. Perhaps I am not following what you suggested correctly?

Did you replace "my_delete_button" with the actual id of your button? (I'm sure you did)

As long as your req() calls are truthy renderUI will re-render when triggered by a button (as its value changes with every click).

Ah, no, I didn't replace "my_delete_button", since I don't know the actual ID of the button. Are we talking about adding a button to the UI and referring to that, or are we talking about using a press of the delete button on my keyboard? Ordinarily I would think the former, but I just got done trying out reactlog, which uses ctrl-F3 (keyboard) to get things rolling.

Ah sorry, I've got that delete button from your previous question. Maybe there is a submit button the user is pressing to update the data? I'm looking for an event related to the edit to trigger the renderUI call.

Thanks for the clarification. There is a button that triggers submission of the changes to the database (which includes reloading the data). I tried putting a reference to the button's input value in the renderUI call, but the bug persisted.

Then the laziness is initiated somewhere upstream of the renderUI call. I'd try the same with the reactive updating database$Committee.

I'm sure this can be solved with a reproducible example.

I think I may have found a fix (although I'm not sure why it works). It has something to do with the difference between reactiveVal and reactiveValues, which apparently is more than just single variable v. list. I created a reactive value via

dbCom <- reactiveVal()

and updated it every time the Committee table was read from the back end database via

dbCom(database$Committee)

. Inside the renderUI call, I changed

record <- database$Committee[input$display_committee_rows_selected, , drop = FALSE]

to

record <- dbCom()[input$display_committee_rows_selected, , drop = FALSE]

and fixed the updating problem. So apparently database$Committee was not as reactive as I expected (?).

Another thought: Maybe this is a scoping issue.

Is database$Committee defined globally? (outside of the server function?)

If this is the case it could be that your update to database$Committee <- some_data_frame only happens locally and when trying to access it later on the global version is used.

Does this update happen in a reactive context?

Can you please try to update it via database$Committee <<- some_data_frame instead?

1 Like

Thanks!! database$Committee is defined inside the server function (by a function call that generates as output a list that is assigned to database). The update to its value, however, happened inside an event observer, and the updated used <- rather than <<-. Other updates occurred in places where database was passed in as an argument (so database <- ... changed it outside the immediate scope), but I forgot that the event observer does not take arguments (other than what triggers it).

It appears doubling the arrowhead fixed it. Again, thanks for sticking with this.