Editing a reactive DT table that remembers the filtering context without page flickering

Hello!

I've been working on an app that I first described under this link. Since it's growing I'm facing new issues that I'm trying to overcome:

  1. How do make sure that when a user edits the values of the DT table in a filtering context, the table doesn't unfilter?

  2. How to prevent page flickering when the editable table get updated and it's values get populated into the second table to update it?

I've tried a couple solutions I found on the web which eventually boil down to something like this:

    proxy_x2 = dataTableProxy("x2")
    
    observeEvent(input$x2_cell_edit, {
      info = input$x2_cell_edit
      i = info$row
      j = info$col
      v = info$value

      rec_val$iris[i, j] <- isolate(DT::coerceValue(v, rec_val$iris[i, j]))

      DT::replaceData(proxy_x2, rec_val$iris, resetPaging = FALSE, clearSelection = "none")

    })

where I need to use a proxy of the table that is being updated using resetPaging and clearSelection options along with isolate() on top of DT::coerceValue. Unfortunately, none of these really helped in this case. What's the best way of achieving points 1 & 2?

1 Like

Also as an addition I did some testing:

I took this code (link) that show's that functionality and is working without any problems:

library(shiny)
library(DT)
shinyApp(
  ui = fluidPage(
    DTOutput('x1')
  ),
  server = function(input, output, session) {
    x = iris
    x$Date = Sys.time() + seq_len(nrow(x))
    output$x1 = renderDT(x, selection = 'none', rownames = F, editable = T)
    
    proxy = dataTableProxy('x1')
    
    observeEvent(input$x1_cell_edit, {
      info = input$x1_cell_edit
      str(info)
      i = info$row
      j = info$col + 1  # column index offset by 1
      v = info$value
      x[i, j] <<- DT::coerceValue(v, x[i, j])
      replaceData(proxy, x, resetPaging = FALSE, rownames = FALSE)
    })
  }
)

And I changed it to something applicable to my example: with an update button using eventReactive, observe and reactiveValues:

library(shiny)
library(DT)

shinyApp(
  ui = fluidPage(
    actionButton(
      inputId = "go",
      label = "Update"
    ),
    DTOutput('x1')
  ),
  server = function(input, output, session) {
    
    ### This is new ###
    
    evt_go_iris <- eventReactive(input$go, {
      x = iris
      x$Date = Sys.time() + seq_len(nrow(x))
      x <- as.data.frame(x)
    })
    
    rec_val = reactiveValues(df = NULL)
    
    observe({
      rec_val$iris   <- evt_go_iris()
    })
    
    ### This is new ###
    
    output$x1 = renderDT(rec_val$iris, selection = 'none', filter = "top", rownames = F, editable = T)
    
    proxy = dataTableProxy('x1')
    
    observeEvent(input$x1_cell_edit, {
      info = input$x1_cell_edit
      str(info)
      i = info$row
      j = info$col + 1  # column index offset by 1
      v = info$value
      rec_val$iris[i, j] <<- DT::coerceValue(v, rec_val$iris[i, j])
      replaceData(proxy, rec_val$iris, resetPaging = FALSE, rownames = FALSE)
    })
  }
)

After making that change the 1st code doesn't work anymore which points to observe & reactiveValues not being properly applied in this context. I see where the error is but I don't understand it or know how to debug. Hope that helps somebody else understanding my use case!

1 Like

Hi all, eventually managed to solve it myself thanks to the awesome reactlog package.

In case somebody finds that useful I'm pasting the full solution down bottom and I will describe the fix which in the end proved to be quite simple:

Discovered problem in the previous solution

I failed to notice that when I do table edits on a reactive value it then updates all reactive functions that depend on it. If I then use the same reactive value as the input for printing the table (below), the awesome functionality of DT of keeping a proxy of that table and displaying it with all filters context etc. won't take place.

output$x1 = renderDT(rec_val$iris, selection = 'none', filter = "top", rownames = F, editable = T)

Discovered solution

In order to be able to use it effectively I had to use both eventReactive and observeEvent with reactiveValues and use a different source for table rendering. As you see below I have the reactive event that pulls the data (e.g. from DB) which then is wrapped in reactive values and observe event so that I could use that object when subsetting the values of the editable table (can't use a reactive expression there). However, table rendering happens only on the reactive event and the reactive value is used only for storing table edits. Thanks to this I can leverage the proxy feature of DT. Hope somebody will find it useful!

my_event <- eventReactive(input$go, {
      x = iris
      x$Date = Sys.time() + seq_len(nrow(x))
      x <- as.data.frame(x)
    })
    
    rec_val <- reactiveValues(df = NULL)
    
    observe({
      rec_val$df <- my_event()
    })
    
    output$x1 <- renderDT(my_event(), selection = 'none', filter = "top", rownames = F, editable = T)

Full code:

library(shiny)
library(DT)

options(shiny.reactlog = TRUE)

shinyApp(
  ui = fluidPage(
    actionButton(
      inputId = "go",
      label = "Update"
    ),
    DTOutput('x1')
  ),
  server = function(input, output, session) {
    
    my_event <- eventReactive(input$go, {
      x = iris
      x$Date = Sys.time() + seq_len(nrow(x))
      x <- as.data.frame(x)
    })
    
    rec_val <- reactiveValues(df = NULL)
    
    observe({
      rec_val$df <- my_event()
    })
    
    output$x1 <- renderDT(my_event(), selection = 'none', filter = "top", rownames = F, editable = T)
    
    proxy <- dataTableProxy('x1')
    
    observeEvent(input$x1_cell_edit, {
      info = input$x1_cell_edit
      str(info)
      i = info$row
      j = info$col + 1  # column index offset by 1
      v = info$value
      
      rec_val$df[i, j] <<- DT::coerceValue(v, rec_val$df[i, j])
      replaceData(proxy, rec_val$df, resetPaging = FALSE, rownames = FALSE)
    })
  }
)
4 Likes

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.

If you have a query related to it or one of the replies, start a new topic and refer back with a link.