Skip to content

Instantly share code, notes, and snippets.

@DeepanshKhurana
Last active June 6, 2024 06:23
Show Gist options
  • Save DeepanshKhurana/95027b7130b1be0d7fea6fb9d85df89a to your computer and use it in GitHub Desktop.
Save DeepanshKhurana/95027b7130b1be0d7fea6fb9d85df89a to your computer and use it in GitHub Desktop.
Editable Reactable (with Modals)
library(shiny)
library(reactable)
library(dplyr)
ui <- fluidPage(
actionButton(inputId = "edit",
label = "Edit"),
reactableOutput("table")
)
server <- function(input, output, session) {
data <- mtcars
data$model <- rownames(data)
rownames(data) <- NULL
data <- data |> select(model, everything())
values <- reactiveValues(dataframe = data,
modal_closed = NULL)
selected_row <- reactive({
getReactableState("table")$selected
})
observeEvent(input$edit, {
if (!is.null(selected_row())) {
labels <- colnames(values$dataframe)
values$modal_closed = FALSE
showModal(
modalDialog(
title = "Edit Values",
p(values$dataframe[selected_row(), ]),
textInput(inputId = labels[1],
label = labels[1],
value = values$dataframe[selected_row(), labels[1]]),
textInput(inputId = labels[2],
label = labels[2],
value = values$dataframe[selected_row(), labels[2]]),
textInput(inputId = labels[3],
label = labels[3],
value = values$dataframe[selected_row(), labels[3]]),
easyClose = FALSE,
footer = actionButton("save", "Save")
)
)
}
})
observeEvent(input$save, {
values$modal_closed <- TRUE
removeModal()
})
observeEvent(values$modal_closed, {
if (values$modal_closed == TRUE) {
values$dataframe[selected_row(), "model"] <- input$model
values$dataframe[selected_row(), "cyl"] <- input$cyl
values$dataframe[selected_row(), "mpg"] <- input$mpg
}
})
reactable_table <- reactive({
reactable(values$dataframe,
selection = "single")
})
output$table <- renderReactable(
reactable_table()
)
}
shinyApp(ui, server)
@cole-johanson
Copy link

@DeepanshKhurana this is really helpful! One thing I noticed is that when you edit something on page 2 (or 3, etc.), the reactable_table() refreshes back to page 1.

To correct for this, I remove reactable_table(), create output$table using data, and use updateReactable(data=values$dataframe, page=current_page()) to maintain the page.

Here is the git diff of my edits:

+  current_page <- reactive({
+    getReactableState("table")$page
+  })
+
   observeEvent(input$edit, {
     if (!is.null(selected_row())) {
       labels <- colnames(values$dataframe)
...
       values$dataframe[selected_row(), "model"] <- input$model
       values$dataframe[selected_row(), "cyl"] <- input$cyl
       values$dataframe[selected_row(), "mpg"] <- input$mpg
+      updateReactable("table", values$dataframe, page = current_page())
     }
   })

-  reactable_table <- reactive({
-    reactable(values$dataframe,
-              selection = "single")
-  })
-
   output$table <- renderReactable(
-    reactable_table()
+    reactable(data, selection = "single")
   )
 }

Here is the full code:

library(shiny)
library(reactable)
library(dplyr)

ui <- fluidPage(
  actionButton(inputId = "edit",
              label = "Edit"),
  reactableOutput("table")
)

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

  data <- mtcars
  data$model <- rownames(data)
  rownames(data) <- NULL
  data <- data |> select(model, everything())

  values <- reactiveValues(dataframe = data,
                           modal_closed = NULL)

  selected_row <- reactive({
    getReactableState("table")$selected
  })

  current_page <- reactive({
    getReactableState("table")$page
  })

  observeEvent(input$edit, {
    if (!is.null(selected_row())) {
      labels <- colnames(values$dataframe)
      values$modal_closed = FALSE
      showModal(
        modalDialog(
        title = "Edit Values",
        p(values$dataframe[selected_row(), ]),
        textInput(inputId = labels[1],
                  label = labels[1],
                  value = values$dataframe[selected_row(), labels[1]]),
        textInput(inputId = labels[2],
                  label = labels[2],
                  value = values$dataframe[selected_row(), labels[2]]),
        textInput(inputId = labels[3],
                  label = labels[3],
                  value = values$dataframe[selected_row(), labels[3]]),
        easyClose = FALSE,
        footer = actionButton("save", "Save")
        )
      )
    }
  })

  observeEvent(input$save, {
    values$modal_closed <- TRUE
    removeModal()
  })

  observeEvent(values$modal_closed, {
    if (values$modal_closed == TRUE) {
      values$dataframe[selected_row(), "model"] <- input$model
      values$dataframe[selected_row(), "cyl"] <- input$cyl
      values$dataframe[selected_row(), "mpg"] <- input$mpg
      updateReactable("table", values$dataframe, page = current_page())
    }
  })

  output$table <- renderReactable(
    reactable(data, selection = "single")
  )
}

shinyApp(ui, server)

@DeepanshKhurana
Copy link
Author

@cole-johanson Fantastic! Thank you for adding and sharing this solution. I did not think of pagination because the table I built this on initially was not paginated. This is an excellent fix for when there is pagination involved. Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment