library(tidyverse)
library(reactable)
<- palmerpenguins::penguins |>
penguins_dat slice_max(body_mass_g, n = 5, by = species) |>
select(species, island, body_mass_g)
|>
penguins_dat reactable(
columns = list(
species = colDef(name = "Species"),
island = colDef(name = "Island"),
body_mass_g = colDef(name = "Weight (g)")
) )
Interactive filters in tables with reactable
reactable
is a great package to make interactive tables in R. And you can make your tables even more interactive by combining them with a tiny bit of JavaScript.
The reactable
package is a powerful tool that simplifies the creation of interactive HTML tables. While it’s quite straightforward to use, it lacks built-in support for dynamic interactions based on the content of a dataset. To show you what I mean have a look at this table. It shows you the five heaviest penguins for each species in the palmerpenguins
dataset.
By default this table can be sorted by the values in each column. Just click on the table names and see how everything gets sorted. But what if you want to filter the table by a specific species or island? This could look something like this.
Notice how the table has a dropdown menu for each of the species
and island
columns. The weight column is filterable too but it has a text search filter instead of a dropdown menu. To create this table, you have to resort to add a bit of JavaScript as well as HTML elements with a bit of help from the htmltools
package. In this blog post, I’ll show you how that works. And if you’re interested in watching the video version of this blog post, you can find it here:
Make table filterable
The first step to create our desired table is to make our original reactable
table filterable. It turns out that reactable
actually has a standard option for that.
|>
penguins_dat reactable(
columns = list(
species = colDef(name = "Species"),
island = colDef(name = "Island"),
body_mass_g = colDef(name = "Weight (g)")
),filterable = TRUE # Make a change here
)
Add reactivity
Notice how the name
argument doesn’t do anything here. So does this dropdown menu. We might change the selected value of the dropdown menu but nothing happens. That’s because we haven’t told anybody what to do when the selection changes. But we can do that now. Just insert the onchange
argument into the tags$select()
function.
<- function(values, name) {
filter_fct $select(
tags$option(value = "", "All"),
tags::map(unique(values), tags$option),
purrronchange = glue::glue("alert('The {name} column changed! Hooray!')")
)
}filter_fct(penguins_dat$species, "species")
Here I’ve use a tiny bit of JavaScript to show a text alert. Whenever you change the selected value of the dropdown menu, the alert will say “The species column changed! Hooray!”. But only because I’ve used the glue
package to insert the name
argument into the JavaScript code.
Change the underlying data with JS
To summarize, we have
- an R function that
- creates JS code that is
- executed when the dropdown menu changes.
Pretty dope if you think about it. Anyway, now it’s time to figure out what the JS code to filter the data is. Let me just tell you: The code for that uses the Reactable.setFilter()
function. How do I know? Well, I found this out in the INSAAAANELY good documentation of the reactable
package.
Now, what we need to understand is that this function takes three arguments:
- the table id,
- the column name and the
- value to filter by.
This means our table needs to have an id. Time to modify the table:
|>
penguins_dat reactable(
columns = list(
species = colDef(name = "Species"),
island = colDef(name = "Island"),
body_mass_g = colDef(name = "Weight (g)")
),elementId = "my-tbl", # Add an id here
filterable = TRUE
)
Now, what we can do is to use the glue
package again to insert the table id and the column name into the JS Reactable.setFilter()
function.
<- function(values, name) {
filter_fct $select(
tags$option(value = "", "All"),
tags::map(unique(values), tags$option),
purrronchange = glue::glue(
"Reactable.setFilter(
'my-tbl',
'{name}',
event.target.value // This is the value of the dropdown menu
)"
)
)
}filter_fct(penguins_dat$species, "species")
Hoooray, we did it! But wait! Why? Are you surprised that we’re already done? Well, try it out. Change the dropdown menu and see how the table changes. The dropdown menu does not have to be inserted into the colDef()
function for all of this to work. The JS code that targets the table with the id my-tbl
will be executed regardless of where the dropdown menu is (as long as it’s on the same web page).
Still, the table looks probably nicer if we put the dropdown menu into the colDef()
function. So let’s do that. But since we create another table, we need to change the id of the table to my-tbl2
. Similarly, we will have to adjust the name inside of filter_fct()
.
<- function(values, name) {
filter_fct $select(
tags$option(value = "", "All"),
tags::map(unique(values), tags$option),
purrronchange = glue::glue(
"Reactable.setFilter(
'my-tbl2', // This is the new ID
'{name}',
event.target.value
)"
)
)
}|>
penguins_dat reactable(
columns = list(
species = colDef(
name = "Species",
filterInput = filter_fct
),island = colDef(
name = "Island",
filterInput = filter_fct
),body_mass_g = colDef(name = "Weight (g)")
),elementId = "my-tbl2", # New id here
filterable = TRUE
)
Nice! We can easily filter our tables now. But we should probably make this look nicer. So let’s add two short lines of CSS code to make the dropdown menus look nicer.
<- function(values, name) {
filter_fct $select(
tags$option(value = "", "All"),
tags::map(unique(values), tags$option),
purrronchange = glue::glue(
"Reactable.setFilter(
'my-tbl3', // Modify id again
'{name}',
event.target.value
)"
),style = "width: 100%; height: 28px;" # Add this line
)
}|>
penguins_dat reactable(
columns = list(
species = colDef(
name = "Species",
filterInput = filter_fct
),island = colDef(
name = "Island",
filterInput = filter_fct
),body_mass_g = colDef(name = "Weight (g)")
),elementId = "my-tbl3", # Modify id again
filterable = TRUE
)
Conclusion
And there you have it, a table that can be filtered by the values of a column using a dropdown menu. This is a great way to make your tables more interactive. Here, we used functions from the htmltools
package to create the dropdown menu. Alternatively, we could also use Observable JS for that. But that’s a story for another time. In the meantime, you may want to check out some of my other content: