rshinyreactable

Add a custom search field in reactable in R Shiny


I am developing an application in R Shiny that displays a table using the reactable package. I am trying to add a custom search field that searches all the columns in the table. Looking at Greg Lin's example codes there is one that fits perfectly ‘custom search input’ but I can't adapt it for Shiny. This is my code:

library(shiny)
library(reactable)
library(htmltools)

ui <- fluidPage(
  
  # Interface elements
  titlePanel("Custom Search in reactable"),
  fluidRow(
    div(
      style = "margin-bottom: 0.75rem",
      tags$input(
        type = "text",
        placeholder = "Search for cars...",
        style = "padding: 0.25rem 0.5rem; width: 100%",
        oninput = "Reactable.setSearch('cars-search-table', this.value)"
      )
    ),
    
    # Show reactable
    div(id = "table-content", reactableOutput("contents"))
  )
)

server <- function(input, output) {
  
  # Render reactable
  output$contents <- renderReactable({
    
    # Load data
    data <- MASS::Cars93[1:15, c("Manufacturer", "Model", "Type", "Price")]
    
    reactable(data,
              defaultPageSize = 5,
              elementId = "cars-search-table")
  })
  
}

shinyApp(ui, server)

My example works perfectly. It uses the elementId to indirectly fill the search field that has the reactable but when I run the application I get this warning:

Warning in renderWidget(instance) : Ignoring explicitly provided widget ID ‘cars-search-table’; Shiny doesn't use them

I don't know if this is the best alternative but I had thought about using shinyjs to change the elementId after the reactable is rendered but I couldn't get it to work properly:

library(shiny)
library(reactable)
library(htmltools)
library(shinyjs)

ui <- fluidPage(
  useShinyjs(),  # Incluir shinyjs en la UI
  
  # Interface elements
  titlePanel("Custom Search in reactable"),
  fluidRow(
    div(
      style = "margin-bottom: 0.75rem",
      tags$input(
        id = "search-input",
        type = "text",
        placeholder = "Search for cars...",
        style = "padding: 0.25rem 0.5rem; width: 100%"
      )
    ),
    
    # Show reactable
    div(id = "table-content", reactableOutput("contents"))
  )
)

server <- function(input, output, session) {
  
  # Render reactable
  output$contents <- renderReactable({
    
    # Load data
    data <- MASS::Cars93[1:15, c("Manufacturer", "Model", "Type", "Price")]
    
    reactable(data,
              defaultPageSize = 5,
              elementId = NULL)  # No usamos elementId aquí
  })
  
  # Modify the table after it has been rendered
  observe({
    runjs("document.querySelector('#table-content > div').id = 'cars-search-table';")
    
    # Connect the search input to the table
    runjs("
      document.getElementById('search-input').oninput = function() {
        Reactable.setSearch('cars-search-table', this.value);
      };
    ")
  })
  
}

shinyApp(ui, server)

UPDATE: as suggested by user @masgusl I tried using crosstalk. This is the code I generated:

library(shiny)
library(reactable)
library(htmltools)
library(crosstalk)

ui <- fluidPage(
  
  # Interface elements
  titlePanel("Custom Search in reactable"),
  fluidRow(
    div(
      style = "margin-bottom: 0.75rem",
      tags$input(
        type = "text",
        id = "search",
        placeholder = "Search for cars...",
        style = "padding: 0.25rem 0.5rem; width: 100%"
      )
    ),
    
    # Show reactable
    div(id = "table-content", reactableOutput("contents"))
  )
)

server <- function(input, output) {
  
  # Load data
  data <- MASS::Cars93[1:15, c("Manufacturer", "Model", "Type", "Price")]
  
  # Create crosstalk shared data object
  shared_data <- SharedData$new(data)
  
  # Create crosstalk filter
  filtered_data <- reactive({
    if (input$search == "") {
      shared_data$data()
    } else {
      shared_data$data() %>%
        filter(if_any(everything(), ~ grepl(input$search, ., ignore.case = TRUE)))
    }
  })
  
  # Render reactable
  output$contents <- renderReactable({
    
    reactable(filtered_data(),
              defaultPageSize = 5
              )
  })
  
}

shinyApp(ui, server)

In this case, the code works perfectly but for a very large dataset, when you filter the data it is quite noticeable the waiting time until the filtered data is displayed. I don't know if the code is optimised.

Could anyone help me to find a solution as simple as possible, please?. Perhaps with a more appropriate solution.

Thank you very much.


Solution

  • Finally, thanks to @Jan's comment, I checked out another post, which in turn led me to the documentation on reactable and how to use the javascript API. This section describes that for Shiny applications, the elementId is taken directly from the Shiny output ID specified in reactableOutput(), so the problem in my example code is solved by removing the elementId and changing the reference in 'Reactable.setSearch'. My code looks like this:

    library(shiny)
    library(reactable)
    library(htmltools)
    
    ui <- fluidPage(
      
      # Interface elements
      titlePanel("Custom Search in reactable"),
      fluidRow(
        div(
          style = "margin-bottom: 0.75rem",
          tags$input(
            type = "text",
            placeholder = "Search for cars...",
            style = "padding: 0.25rem 0.5rem; width: 100%",
            oninput = "Reactable.setSearch('contents', this.value)"
          )
        ),
        
        # Show reactable
        div(id = "table-content", reactableOutput("contents"))
      )
    )
    
    server <- function(input, output) {
      
      # Render reactable
      output$contents <- renderReactable({
        
        # Load data
        data <- MASS::Cars93[1:15, c("Manufacturer", "Model", "Type", "Price")]
        
        reactable(data,
                  defaultPageSize = 5)
      })
      
    }
    
    shinyApp(ui, server)
    

    Knowing this, the solution was very simple and without the need to use crosstalk. At least in this case.

    Thanks to all of you for your comments. In the end, you all helped me to look for other alternatives that I will be able to use sometime.

    Thanks again
    Wardiam