rshinyrowid

Add row number column to a reactive data frame in Shiny


I am trying to add a row index column to a reactive data frame created on-the-fly from user inputs. I am able to do this outside of Shiny using the tibble::rowid_to_column function but cannot make it work in the below Shiny app (line 44). Can someone please provide guidance on how to make it work? Also, when I delete a row from the data frame, how can we make rowid numbers sequential again? Thanks.

library(shiny)
library(DT)
library(tidyverse)


input_data <- data.frame(
  # rowid = double(),
  input1 = character(),
  input2 = double(),
  stringsAsFactors = FALSE)

ui <- fluidPage(
  
  titlePanel("Title"),
  
  sidebarLayout(
    sidebarPanel(
      selectInput("input1",
                  "Input 1",
                  choices = c("Value 1", "Value 2", "Value 3")),
      numericInput("input2",
                   "Input 2",
                   value = 100),
      actionButton("add_btn",
                   "Add Row"),
      actionButton("delete_btn", 
                   "Delete Row"),
      actionButton("reset_btn", 
                   "Reset"),
      position = "left"
    ),
    
    mainPanel(
      DT::dataTableOutput("input_table")
    )
  )
)

server <- function(input, output) {
  input_table <- reactiveVal(input_data)
  observeEvent(input$add_btn, {
    t = rbind(input_table(), data.frame(col1 = input$input1, col2 = input$input2)) 
    # %>%
    #   cbind(tibble::rowid_to_column("rowid"))
    input_table(t)
  })
  
  observeEvent(input$delete_btn, {
    t = input_table()
    print(input$input_table_rows_selected)
    if (!is.null(input$input_table_rows_selected)) {
      t <- t[-input$input_table_rows_selected,]
    }
    input_table(t)
  })
  
  observeEvent(input$reset_btn, {
    input_table(input_data)
  })
  
  output$input_table <- DT::renderDataTable({
    datatable(input_table())
  })
}

shinyApp(ui = ui, server = server)

Solution

  • rowid_to_column() adds the row names of a tibble and adds them as a column to the data frame. This won't work for you: once you have added the rownames in a column rowid, you cannot add that column a second time. Also, the function returns the entire tibble with the new column added, so it makes no sense to cbind() the output of rowid_to_column() it to the tibble.

    I suggest the following changes to your code:

    Define the initial Table with the rowid column:

    input_data <- tibble(
      rowid = integer(),
      input1 = character(),
      input2 = double()
    )
    

    In the first observer, change the code to this:

    observeEvent(input$add_btn, {
        new_row <- tibble(rowid = nrow(input_table()) + 1,
                          input1 = input$input1,
                          input2 = input$input2)
        t = bind_rows(input_table(), new_row)
        input_table(t)
      })
    

    This creates a new row with the appropriate rowid and then adds it to the table.

    In order to have the expected rowids after deletion of a row you simply have to redefine the rowids each time a row is delted:

    observeEvent(input$delete_btn, {
      t <- input_table()
      print(input$input_table_rows_selected)
      if (!is.null(input$input_table_rows_selected)) {
        t <- t[-input$input_table_rows_selected, ]
        # reset the rowids only, when there is at least one row left
        if (nrow(t) > 0) {
          t$rowid <- 1:nrow(t)
        }
      }
      input_table(t)
    })
    

    And when rendering the output table, you have to suppress the row names:

    output$input_table <- DT::renderDataTable({
      datatable(input_table(), rownames = FALSE)
    })