reactable.extras 0.2.0 Release: Enhanced Interactivity and Efficiency for Shiny Apps
We are thrilled to announce the release of <a href="https://appsilon.github.io/reactable.extras/" target="_blank" rel="noopener"><code>reactable.extras</code></a>, which is now available for seamless integration into your Shiny applications, marking its debut as a significant release on <a href="https://cran.r-project.org/package=reactable.extras" target="_blank" rel="noopener noreferrer">CRAN</a>.
With <code>reactable.extras</code>, you can elevate your data tables to new heights, <strong>simplifying the management of interactive components</strong> within <code>reactable</code> tables, offering even more <strong>enhanced functionality</strong> and a <strong>smoother user experience</strong>.
<h3>Table of Contents</h3>
<ul><li><a href="#choose-reactables">Why Choose <code>reactable.extras</code>?</a></li><li><a href="#install">Installing the Package</a></li><li><a href="#server-side">Server-Side Processing</a></li><li><a href="#custom-inputs">Custom Inputs</a></li><li><a href="#explore-tools">Explore More Open-Source Tools for Your Shiny Projects</a></li></ul>
<h2 id="choose-reactables">Why Choose reactable.extras?</h2>
<a href="https://glin.github.io/reactable/" target="_blank" rel="noopener noreferrer"><code>reactable</code></a> is known for creating interactive, feature-rich tables in Shiny applications. With <code>reactable.extras</code>, you can take your <code>reactable</code> tables to the next level by adding custom inputs, server-side rendering and more:
<strong>Custom Inputs:</strong> <code>reactable.extras</code> incorporate custom inputs within <code>reactable</code> data tables. Supported input types include text input, buttons, dropdowns, dates, and checkboxes. You can now create interactive data tables that respond to user inputs with ease.
<video width="100%" height="auto" src="https://wordpress.appsilon.com/wp-content/uploads/2023/11/image3.webm" loop="true" autoplay="true" controls="true"></video>
<strong>Server-side processing:</strong> <code>reactable.extras</code> renders only a subset of large data in the server memory. This almost instantly renders the desired page and keeps the amount of memory used in the browser minimal.
<h2 id="install">Installing the Package</h2>
<ul><li>Install the <code>reactable.extras</code> package:</li></ul>
<pre><code>
install.packages(“reactable.extras”)
</code></pre>
<ul><li>After <a href="https://github.com/Appsilon/reactable.extras#how-to-install" target="_blank" rel="noopener">installation</a>, you can simply load <code>reactable.extras</code>:</li></ul>
<pre><code>
library(reactable.extras)
</code></pre>
<h2 id="server-side">Server-Side Processing</h2>
Rendering a <code>reactable</code> with a lot of data can be inefficient. The initial loading will take some time, and a lot of memory will be thrown into the browser.
A more efficient approach is to render only the data that is needed to be displayed on a single page of a table.
<code>reactable_extras_ui()</code> and <code>reactable_extras_server()</code> is a wrapper for <code>reactable::reactableOutput()</code> and <code>reactable::renderReactable({reactable(...)})</code>.
It renders only a subset of large data in the server memory. This almost instantly renders the desired page and keeps the amount of memory used in the browser minimal.
<strong>Consider this example data:</strong>
<pre><code class="language-r">
library(shiny)
library(reactable)
library(reactable.extras)
<br>
mtcars_ultra <- purrr::map(
seq(1L, 20000L, by = 1L),
~ {
temp_df <- mtcars
temp_df$make <- rownames(temp_df)
rownames(temp_df) <- NULL
temp_df <- dplyr::mutate(temp_df, id_row = paste0("id_", dplyr::row_number(), "_", .x)) temp_df }, .progress = TRUE ) |>
purrr::list_rbind()
</code></pre>
<img class="wp-image-22027 size-full" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b01969725ded127af0ae4a_pasted-image-0-1.webp" alt="A screenshot of a data table containing automotive information, including miles per gallon, cylinders, displacement, horsepower, and other car specifications, with an indication of '1 of 40000' suggesting a large dataset." width="1600" height="973" /> Sample Data: Automotive Specifications Table
And compare the difference in initial load time and amount of memory used in the browser when loading all the data at once vs loading only the data needed for the page.
<h3>App 1 (Rendering All Data at Once)</h3>
<img class="size-full wp-image-22091" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b0196a7fda7d9cd7da9cef_full-data-rendered.gif" alt="" width="1916" height="1080" /> Full Data Rendered
We’ll create a simple interactive table to show the dataset <em>mtcars_ultra</em>. This table will have three columns labelled “Miles per Gallon”, “Cylinders”, and “Displacement”. This table will show only 16 rows of data at a time, however, it will try to load all the data at once, which could slow down the webpage.
<pre><code class="language-r">
# All of the data rendered all at once
shinyApp(
ui = reactableOutput("test"),
server = function(input, output) {
output$test <- renderReactable({
reactable(
data = mtcars_ultra,
columns = list(
mpg = colDef(name = "Miles per Gallon"),
cyl = colDef(name = "Cylinders"),
disp = colDef(name = "Displacement")
),
defaultPageSize = 16
)
})
}
)
</code></pre>
<h3>App 2 (Server-Side Processing)</h3>
<img class="size-full wp-image-22093" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b0196b20852b3d3e12f91b_server-side-processing.gif" alt="" width="1914" height="1078" /> Server Side Processing
Here we’re creating the same table, however, this one is smarter with large datasets such as <em>mtcars_ultra</em>. Instead of loading all the data at once, it only loads a portion of the data that’s needed for the current page view. This makes it quicker and more memory-efficient as it’s set up to handle a large number of pages efficiently with <code>reactable_extras</code>.
<pre><code class="language-r">
# Only the subset of the data needed for the page is rendered
shinyApp(
ui = reactable_extras_ui("test"),
server = function(input, output, session) {
reactable_extras_server(
"test",
data = mtcars_ultra,
columns = list(
mpg = colDef(name = "Miles per Gallon"),
cyl = colDef(name = "Cylinders"),
disp = colDef(name = "Displacement")
),
total_pages = 4e4
)
}
)
</code></pre>
<h2 id="custom-inputs">Custom Inputs</h2>
You can use custom inputs inside your <code>reactable</code> column. Custom inputs can significantly enhance the interactivity and functionality of tables in your web applications. For instance, there may be scenarios where you need to allow users to edit values directly within a table, toggle settings, or make selections via dropdown menus without leaving the context of the data they're working with. <code>reactable.extras</code> offers a <strong>seamless way to integrate these interactive elements directly into your <code>reactable</code> tables</strong>.
<h4><strong>Supported types for now:</strong></h4><ul><li>text input: text_extra</li><li>button: button_extra</li><li>dropdown: dropdown_extra</li><li>date: date_extra</li><li>checkbox: checkbox_extra</li><li>tooltips: tooltip_extra</li></ul>
<strong>It’s possible to apply additional styling to your inputs by passing the class argument:</strong>
<pre><code>
checkbox_extra("check", class = "checkbox-extra")
</code></pre>
<h3>With Styling</h3>
<img class="size-full wp-image-22036" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b0196d3b97b66bbdcfaa8d_with-styling.webp" alt="Screenshot of a data table with car information including Manufacturer, Model, Type, Minimum Price, Date, and a Check column with checkboxes, some of which are marked with green checks." width="1600" height="633" /> Car Inventory Checklist with Pricing and Dates
<pre><code>
/* Default style for checkboxes */
.checkbox-extra {
width: 20px;
height: 20px;
border: 2px solid grey; /* Grey border */
border-radius: 5px;
appearance: none; /* Remove default checkbox style */
background-color: white; /* White background */
}
<br>/* Style when checkbox is checked */
.checkbox-extra:checked {
background-color: #4CAF50; /* Green background */
border-color: #4CAF50; /* Green border */
}
</code></pre>
<strong>Remember to correctly link the CSS file in your Shiny app:</strong>
<pre><code class="language-r">
ui <- fluidPage(
tags$head(
tags$link(rel = "stylesheet", type = "text/css", href = "custom_styles.css")
),
# ... rest of your UI elements ...
)
</code></pre>
<h3>Without Styling</h3>
<img class="size-full wp-image-22038" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b0196ee64b1cc59a11650d_without-styling.webp" alt="A data table with columns for Manufacturer, Model, Type, Minimum Price, Date, and a Check column with purple checkmarks in some rows." width="1600" height="611" /> Car Inventory Data Table with Selection Checkmarks
<strong>Also, it’s important to import JavaScript dependencies by adding to UI:</strong>
<pre><code>
reactable.extras::reactable_extras_dependency()
</code></pre>
All events of your inputs will be tracked and can be used in your shiny server.
<h4>Example application:</h4>
<video width="100%" height="auto" src="https://wordpress.appsilon.com/wp-content/uploads/2023/11/Screen-Recording-2023-11-12-at-11.55.55-PM-1.webm" loop="true" autoplay="true" controls="true"></video>
<pre><code class="language-r">
library(shiny)
library(reactable)
library(reactable.extras)
<br># Preparing the test data
df <- MASS::Cars93[, 1:4]
df$Date <- sample(
seq(as.Date("2020/01/01"), as.Date("2023/01/01"), by = "day"),
nrow(df)
)
df$Check <- sample(c(TRUE, FALSE), nrow(df), TRUE)
<br># Helper function for string formatting
string_list <- function(values) {
paste0("{", paste0(names(values), " : ", unlist(values), collapse = ", "), "}")
}
<br># Shiny app
shinyApp(
ui = fluidPage(
reactable_extras_dependency(),
reactableOutput("react"),
hr(),
textOutput("date_text"),
textOutput("button_text"),
textOutput("check_text"),
textOutput("dropdown_text"),
textOutput("text")
),
server = function(input, output) {
output$react <- renderReactable({
reactable(
df,
columns = list(
Manufacturer = colDef(cell = button_extra(id = "button", class = "button-extra")),
Check = colDef(cell = checkbox_extra(id = "check", class = "checkbox-extra"), align = "left"),
Date = colDef(cell = date_extra(id = "date", class = "date-extra")),
Type = colDef(cell = dropdown_extra(id = "dropdown", unique(df$Type), class = "dropdown-extra")),
Model = colDef(cell = text_extra(id = "text"))
)
)
})
<br> # Render text outputs
output_text_render <- function(id) {
output[[id]] <- renderText({
req(input[[id]])
paste0(id, ": ", string_list(input[[id]]))
})
}
<br> lapply(c("date_text", "button_text", "check_text", "dropdown_text", "text"), output_text_render)
}
)
</code></pre>
<h4>You can also use <code>tippy</code> to add tooltips to your <code>reactable</code> columns:</h4>
We have implemented tooltips in our application using <a href="https://atomiks.github.io/tippyjs/" target="_blank" rel="noopener">Tippy.js</a>, a JavaScript library designed for creating interactive and customizable tooltips.
For example, let’s build an interactive table displaying car data. We will utilize the <code>reactable.extras</code> package and tippy to add tooltips to table headers. When users hover over headers like "Manufacturer" or "Check," tooltips provide additional context, such as "Manufacturer type" or "Checkbox."
This enhances the app’s interactivity and user experience as it offers more information directly to the UI without cluttering the view.
<video width="100%" height="auto" src="https://wordpress.appsilon.com/wp-content/uploads/2023/11/Screen-Recording-2023-11-06-at-9.45.41-AM-1.webm" loop="true" autoplay="true" controls="true"></video>
<pre><code class="language-r">
library(shiny)
library(reactable)
library(reactable.extras)
<br>df <- MASS::Cars93[, 1:4]
df$Date <- sample(seq(as.Date("2020/01/01"), as.Date("2023/01/01"), by = "day"), nrow(df))
df$Check <- sample(c(TRUE, FALSE), nrow(df), TRUE)
<br>shinyApp(
ui = fluidPage(
reactable_extras_dependency(),
reactable_extras_ui("table"),
hr(),
textOutput("date_text"),
textOutput("button_text"),
textOutput("check_text"),
textOutput("dropdown_text"),
textOutput("text")
),
server = function(input, output) {
reactable_extras_server(
"table",
data = df,
columns = list(
Manufacturer = colDef(
header = tooltip_extra(content = "Manufacturer type"),
cell = button_extra("button")
),
Check = colDef(
header = tooltip_extra(content = "Checkbox"),
cell = checkbox_extra("check"),
align = "left"
),
Date = colDef(
header = tooltip_extra(content = "Date input"),
cell = date_extra("date")
),
Type = colDef(
header = tooltip_extra(content = "Type dropdown"),
cell = dropdown_extra("dropdown", unique(df$Type))
),
Model = colDef(
header = tooltip_extra(content = "Model input"),
cell = text_extra("text")
)
)
)
}
)
</code></pre>
<h2>Combine reactable functionalities</h2>
Let’s build a Shiny app that leverages the <code>reactable</code> and <code>reactable.extras</code> packages to present an interactive table that allows users to filter, search, and select multiple rows within a dataset of 1000 entries.
Each entry has an ID, SKU number, action status, and registration date. Custom buttons are integrated into the 'Actions' column, and a date input is provided for the 'Registered' column for enhanced interactivity.
Additionally, the UI includes buttons for users to select specific rows, clear selections, and navigate directly to the third page of the table. The server logic handles these interactive features by observing button clicks and updating the table accordingly, allowing for a dynamic user experience.
<video width="100%" height="auto" src="https://wordpress.appsilon.com/wp-content/uploads/2023/11/Screen-Recording-2023-11-06-at-9.21.22-AM-1.webm" loop="true" autoplay="true" controls="true"></video>
<pre><code class="language-r">
library(shiny)
library(reactable)
library(reactable.extras)
<br>
data <- data.frame(
ID = 1:1000,
SKU_Number = paste0("SKU ", 1:1000),
Actions = rep(c("Updated", "Initialized"), times = 20),
Registered = as.Date("2023/1/1")
)
<br>
ui <- fluidPage(
# Include reactable.extras in your UI
reactable_extras_dependency(),
actionButton("select_btn", "Select rows"),
actionButton("clear_btn", "Clear selection"),
actionButton("page_btn", "Change to page 3"),
reactableOutput("table")
)
<br>
server <- function(input, output, session) {
output$table <- renderReactable({
# Create a reactable table with enhanced features
reactable(
data,
filterable = TRUE,
searchable = TRUE,
selection = "multiple",
columns = list(
ID = colDef(name = "ID"),
SKU_Number = colDef(name = "SKU_Number"),
Actions = colDef(
name = "Actions",
cell = button_extra("button", class = "btn btn-primary")
),
Registered = colDef(
cell = date_extra("Registered", class = "date-extra")
)
)
)
})
observeEvent(input$select_btn, {
# Select rows
updateReactable("table", selected = c(1, 3, 5))
})
observeEvent(input$clear_btn, {
# Clear row selection
updateReactable("table", selected = NA)
})
observeEvent(input$page_btn, {
# Change current page
updateReactable("table", page = 3)
})
}
<br>
shinyApp(ui, server)
</code></pre>
<h2 id="explore-tools">Explore More Open-Source Tools for Your Shiny Projects</h2>
<code>reactables.extras</code> provides an <strong>improved user satisfaction</strong> for customer-facing apps, <strong>optimizes the handling of large datasets</strong> with reduced strain on client-side resources and serves as a <strong>cost-effective solution</strong> (reduced server load and optimized memory usage).
Be sure to head over to the <a href="https://rhinoverse.dev/#rhino" target="_blank" rel="noopener">Rhinoverse</a> to explore more open-source tools for your Shiny project. You can find more resources like tutorials and documentation on <code>reactable.extras</code> in Shiny on the <a href="https://appsilon.github.io/reactable.extras/" target="_blank" rel="noopener"><strong>reactable.extras page</strong></a>.
If you find value in these open-source packages, be sure to give them a star on <a href="https://github.com/Appsilon/reactable.extras" target="_blank" rel="noopener">GitHub</a>. That lets us know we’re on the right path toward serving you and the fellow R Shiny community. And, of course, if you have feedback or need assistance getting started, <a href="https://appsilon.com/#contact">reach out</a>!