rshinyreactivedtpersistent

Persistent data in reactive editable table in Shiny app using DT


I have an app, which fetches data from an SQL-db, then allows the user to edit it, and this should be saved to the DB. In the repex I have used a CSV-file, but the logic should still be comparable.

However, the data is saved in the session once I edit the column value, but if I switch input or close the app and re-open, it's back to the original. Edits are not reflected in the summary table. What am I doing wrong?

# Load libraries
library(DT)
library(gt)
library(shiny)
library(shinydashboard)
library(dplyr)

# Load data (run once for replication; in real use case will be a DB-connection)
#gtcars_tbl <- gtcars
#write.csv(gtcars_tbl, "gtcars_tbl.csv", row.names = FALSE)

# Simple UI
sidebar <- dashboardSidebar(
  sidebarMenu(
    menuItem("Summary table", tabName = "summary", icon = icon("project-diagram")),
    menuItem("Edit table", tabName = "edit", icon = icon("project-diagram")),
    uiOutput("country")
  )
)

body <- dashboardBody(
  tabItems(
    tabItem(tabName = "summary",
            h2("Summary of GT Cars"),
            gt_output(outputId = "gt_filt_tbl")
    ),
    
    tabItem(tabName = "edit",
            h2("Editer GT Cars"),
            DTOutput("edit")
    )
  )
)

ui <- dashboardPage(
  dashboardHeader(title = "GT Cars"),
  sidebar,
  body)


# Define server functions

server <- function(input, output, session) {

  # Load data
  gtcars_tbl <- read.csv("gtcars_tbl.csv")  
  
  countries <- sort(as.vector(unique(gtcars_tbl$ctry_origin)))
  
  # Create dropdown output
  output$country <- renderUI({
    selectInput("country", "Country", countries)
  })

  # Create reactive table
  
  gt_tbl_react <- reactiveVal(NULL)
  gt_tbl_react(gtcars_tbl)
  
  # Create filtered table
  gt_filt_tbl <- reactive({
    req(input$country)
    gt_tbl_react() %>%
      filter(ctry_origin == input$country)
    
  })

  # Render summary table
  output$gt_filt_tbl <- render_gt({
    gt_filt_tbl() %>%
      group_by(ctry_origin, mfr) %>%
      summarise(
        N = n(),
        Avg_HP = mean(hp),
        MSRP = mean(msrp)
      ) %>%
      gt(
        rowname_col = "ctry_origin",
        groupname_col = "mfr")
      
  }) 

  # Render editable table
  
  output$edit <- renderDT(
    gt_tbl_react() %>%
      filter(ctry_origin == input$country),
    selection = 'none', editable = TRUE, 
    rownames = TRUE,
    extensions = 'Buttons'
  )
  
  observeEvent(input$edit_cell_edit, {
    gtcars_tbl[input$edit_cell_edit$row,input$edit_cell_edit$col] <<- input$edit_cell_edit$value
    write.csv(gtcars_tbl, "gtcars_tbl.csv", row.names = FALSE)
  })
  
}

# Run app
shinyApp(ui, server)

Solution

  • The issue is that input$edit_cell_edit$row and input$edit_cell_edit$col are provided according to the subsetted dataframe that is displayed whereas you are changing the values on complete dataframe.

    Use this in observeEvent -

    observeEvent(input$edit_cell_edit, {
        inds <- which(gtcars_tbl$ctry_origin == input$country)
        gtcars_tbl[inds[input$edit_cell_edit$row],input$edit_cell_edit$col] <- input$edit_cell_edit$value
        write.csv(gtcars_tbl, "gtcars_tbl.csv", row.names = FALSE)
      })