Custom filter methods and custom filter inputs let you change how filtering is done in reactable. By default, all filter inputs are text inputs that filter data using a case-insensitive text match, or for numeric columns, a prefix match.
Column filter methods, table search methods, and column filter inputs can all be customized separately for flexibility. In some cases, you may want to keep the default filter input but change the filter method, or in other cases, vice versa.
Column Filter Methods
Column filter methods can be customized using the
filterMethod
argument in colDef()
:
colDef(
filterMethod = JS("
function(rows, columnId, filterValue) {
/* ... */
return filteredRows
}
")
)
filterMethod
should be a JavaScript function, wrapped in
JS()
, that takes 3 arguments — the rows, column ID, and
filter value — and returns the filtered array of rows.
Arguments
-
rows
, an array of row objects.rows
consists of data rows only, and does not include aggregated rows when the table is grouped. EachRow
has the following properties:Property Example Description values
{ Petal.Length: 1.7, Species: "setosa" }
row data values index
20
row index (zero-based) columnId
, the column ID. Use this to access the cell value, likerow.values[columnId]
.filterValue
, the column filter value. This will be a string when using the default filter inputs.
Table Search Methods
The global table search method can be customized using the
searchMethod
argument in reactable()
:
reactable(
searchMethod = JS("
function(rows, columnIds, filterValue) {
/* ... */
return filteredRows
}
")
)
searchMethod
should be a JavaScript function, wrapped in
JS()
, that takes 3 arguments — the rows, column IDs, and
search value — and returns the filtered array of rows.
Arguments
-
rows
, an array of row objects.rows
consists of data rows only, and does not include aggregated rows when the table is grouped. EachRow
has the following properties:Property Example Description values
{ Petal.Length: 1.7, Species: "setosa" }
row data values index
20
row index (zero-based) columnIds
, an array of column IDs. Use this to access the cell values for the columns being searched, likerow.values[columnId]
.searchValue
, the search value. This will be a string when using the default search input.
Column Filter Inputs
Column filter inputs are customized using the
filterInput
argument in colDef()
.
filterInput
can either be an element or a function that
returns an element to be rendered in place of the default column
filter.
Unlike custom rendering of other table elements, custom filter inputs must communicate back to the table on filtering changes, which can be tricky.
In many simpler cases, you can write your custom filter input in R
and use Reactable.setFilter()
from the reactable JavaScript API to notify the table of filter changes.
Note that the table must have a unique elementId
to use
Reactable.setFilter()
— see Using the JavaScript
API for more details.
For more advanced customization, you can write your filter input
entirely in JavaScript, using the React JavaScript library to create an
element that gets and sets the filter value. In reactable, JavaScript
render functions can return React elements, and reactable comes with
React as a dependency via the reactR package. Use
reactR::html_dependency_react()
to explicitly include this
dependency or find the version of React in use.
For more information on React or using React from R, see the React documentation and reactR package documentation.
Tip: Many column filter inputs do not have a visible
text label, including the default text inputs. If your custom filter
does not have a visible text label, be sure to give it an accessible
name using the aria-label
attribute or similar technique.
R render function
R render functions take up to 2 optional arguments — the column values and column name — and return the element to render.
colDef(
filterInput = function(values, name) {
# input:
# - values, the column values (optional)
# - name, the column name (optional)
#
# output:
# - element to render (e.g. an HTML tag or HTML string)
htmltools::tags$input(
type = "text",
onchange = sprintf("Reactable.setFilter('tbl', '%s', this.value)", name),
"aria-label" = sprintf("Filter %s", name),
style = "width: 100%;"
)
}
)
JavaScript render function
JavaScript render functions, wrapped in JS()
, take up to
2 optional arguments — a column object and a table state object — and
return the element to render.
colDef(
filterInput = JS("
function(column, state) {
// input:
// - column, an object containing column properties
// - state, an object containing the table state
//
// output:
// - element to render (e.g. an HTML string or React element)
return React.createElement('input', {
type: 'text',
value: column.filterValue,
onChange: function(e) {
return column.setFilter(e.target.value || undefined)
},
'aria-label': 'Filter ' + column.name
})
}
")
)
column
properties
Property | Example | Description |
---|---|---|
id
|
"Petal.Length"
|
column ID |
name
|
"Petal Length"
|
column display name |
filterValue
|
"petal"
|
column filter value |
setFilter
|
function setFilter(value: any)
|
function to set the column filter value
(set to
undefined
to clear the filter)
|
state
properties
Property | Example | Description |
---|---|---|
sorted
|
[{ id: "Petal.Length", desc: true }, ...]
|
columns being sorted in the table |
page
|
2
|
page index (zero-based) |
pageSize
|
10
|
page size |
pages
|
5
|
number of pages |
filters
|
[{ id: "Species", value: "petal" }]
|
column filter values |
searchValue
|
"petal"
|
table search value |
selected
|
[0, 1, 4]
|
selected row indices (zero-based) |
pageRows
|
[{ Petal.Length: 1.7, Species: "setosa" }, ...]
|
current row data on the page |
sortedData
|
[{ Petal.Length: 1.7, Species: "setosa" }, ...]
|
current row data in the table (after sorting, filtering, grouping) |
data
|
[{ Petal.Length: 1.7, Species: "setosa" }, ...]
|
original row data in the table |
hiddenColumns
|
["Petal.Length"]
|
columns being hidden in the table |
Examples
Basic custom filter method
This example shows basic usage of a custom filter method, changing
filtering on the Manufacturer
column to be case-sensitive
rather than case-insensitive. (Try filtering for “bmw” and then
“BMW”).
data <- MASS::Cars93[, c("Manufacturer", "Model", "Type", "Price")]
reactable(
data,
columns = list(
Manufacturer = colDef(
filterable = TRUE,
# Filter by case-sensitive text match
filterMethod = JS("function(rows, columnId, filterValue) {
return rows.filter(function(row) {
return row.values[columnId].indexOf(filterValue) !== -1
})
}")
)
),
defaultPageSize = 5
)
Basic custom search method
This example shows basic usage of a custom search method, changing global searching to be a case-sensitive text match on all columns. (Try searching for “bmw” and then “BMW”).
Note that some columns may be numeric or another non-string type, so
you can use String()
to convert values to strings before calling string methods like indexOf()
.
data <- MASS::Cars93[, c("Manufacturer", "Model", "Type", "Price")]
reactable(
data,
searchable = TRUE,
# Search by case-sensitive text match
searchMethod = JS("function(rows, columnIds, searchValue) {
return rows.filter(function(row) {
return columnIds.some(function(columnId) {
return String(row.values[columnId]).indexOf(searchValue) !== -1
})
})
}"),
defaultPageSize = 5
)
Exact text match
This example shows how you can filter a column using a case-sensitive exact text match. (Try searching for “BMW” and then “bmw”).
Note that some columns may be numeric or another non-string type, so
you can use String()
to convert values to strings before comparing them with the filter
value.
data <- MASS::Cars93[, c("Manufacturer", "Model", "Type", "Price")]
reactable(
data,
columns = list(
Manufacturer = colDef(
filterable = TRUE,
# Filter by case-sensitive exact text match
filterMethod = JS("function(rows, columnId, filterValue) {
return rows.filter(function(row) {
return String(row.values[columnId]) === filterValue
})
}")
)
),
defaultPageSize = 5
)
Numeric value filtering
This example shows how you can filter a numeric column based on a
minimum value. (Try filtering the Price
column for
30
).
data <- MASS::Cars93[, c("Manufacturer", "Model", "Type", "Price")]
reactable(
data,
columns = list(
Price = colDef(
filterable = TRUE,
# Filter by minimum price
filterMethod = JS("function(rows, columnId, filterValue) {
return rows.filter(function(row) {
return row.values[columnId] >= filterValue
})
}")
)
),
defaultPageSize = 5
)
Regular expression pattern filtering
This example shows how you can filter a column based on a regular expression pattern. Note that the regular expression is not escaped here — see regular expression escaping for an example of how to escape special characters in regular expressions.
data <- MASS::Cars93[, c("Manufacturer", "Model", "Type", "Price")]
reactable(
data,
columns = list(
Manufacturer = colDef(
filterable = TRUE,
# Filter by case-insensitive text match
filterMethod = JS("function(rows, columnId, filterValue) {
const pattern = new RegExp(filterValue, 'i')
return rows.filter(function(row) {
return pattern.test(row.values[columnId])
})
}")
)
),
defaultPageSize = 5
)
Fuzzy text searching
This example uses the match-sorter JavaScript library to add fuzzy searching to a table. (Try searching “adi” to match both “Cadillac” and “Audi”).
This also shows how you can create reusable search or filter methods that can be shared across multiple tables or columns.
library(htmltools)
data <- MASS::Cars93[, c("Manufacturer", "Model", "Type", "Price")]
# match-sorter library dependency. Include this anywhere in your document or app.
matchSorterDep <- htmlDependency(
"match-sorter",
"1.8.0",
c(href = "https://unpkg.com/match-sorter@1.8.0/dist/umd/"),
script = "match-sorter.min.js"
)
# Fuzzy search method based on match-sorter
# See https://github.com/kentcdodds/match-sorter for advanced customization
matchSorterSearchMethod <- JS("function(rows, columnIds, searchValue) {
const keys = columnIds.map(function(id) {
return function(row) {
return row.values[id]
}
})
return matchSorter(rows, searchValue, { keys: keys })
}")
browsable(tagList(
matchSorterDep,
reactable(
data,
searchable = TRUE,
searchMethod = matchSorterSearchMethod,
defaultPageSize = 5
)
))
Select input filter
This example shows how you can render a custom <select>
input filter in R.
The <select>
input filters the
Manufacturer
column from a set of unique values, and
includes an additional “All” option to clear the filter.
library(htmltools)
data <- MASS::Cars93[, c("Manufacturer", "Model", "Type", "Price")]
reactable(
data,
filterable = TRUE,
columns = list(
Manufacturer = colDef(
filterInput = function(values, name) {
tags$select(
# Set to undefined to clear the filter
onchange = sprintf("Reactable.setFilter('cars-select', '%s', event.target.value || undefined)", name),
# "All" has an empty value to clear the filter, and is the default option
tags$option(value = "", "All"),
lapply(unique(values), tags$option),
"aria-label" = sprintf("Filter %s", name),
style = "width: 100%; height: 28px;"
)
}
)
),
defaultPageSize = 5,
elementId = "cars-select"
)
Data list filter
The <datalist>
element is like a native text input, but with an autocomplete
feature that lets you choose from a set of unique options. (Try
searching for “ac” in the Manufacturer
column.)
This example also shows how you can create reusable filter inputs, or set default custom filters based on column type.
library(htmltools)
data <- MASS::Cars93[, c("Manufacturer", "Model", "Type", "Price")]
# Creates a data list column filter for a table with the given ID
dataListFilter <- function(tableId, style = "width: 100%; height: 28px;") {
function(values, name) {
dataListId <- sprintf("%s-%s-list", tableId, name)
tagList(
tags$input(
type = "text",
list = dataListId,
oninput = sprintf("Reactable.setFilter('%s', '%s', event.target.value || undefined)", tableId, name),
"aria-label" = sprintf("Filter %s", name),
style = style
),
tags$datalist(
id = dataListId,
lapply(unique(values), function(value) tags$option(value = value))
)
)
}
}
reactable(
data,
filterable = TRUE,
columns = list(
# Use data list filter for a specific column
Manufacturer = colDef(
filterInput = dataListFilter("cars-list")
)
),
# Or use data list filter as the default for all factor columns
defaultColDef = colDef(
filterInput = function(values, name) {
if (is.factor(values)) {
dataListFilter("cars-list")(values, name)
}
}
),
defaultPageSize = 5,
elementId = "cars-list"
)
Range filter
This is a basic example of a native <input type="range">
element for numeric filtering. The Price
column is
filtered by minimum value.
library(htmltools)
data <- MASS::Cars93[, c("Manufacturer", "Model", "Type", "Price")]
reactable(
data,
columns = list(
Price = colDef(
filterable = TRUE,
filterMethod = JS("function(rows, columnId, filterValue) {
return rows.filter(function(row) {
return row.values[columnId] >= filterValue
})
}"),
filterInput = function(values, name) {
oninput <- sprintf("Reactable.setFilter('cars-range', '%s', this.value)", name)
tags$input(
type = "range",
min = floor(min(values)),
max = ceiling(max(values)),
value = floor(min(values)),
oninput = oninput,
onchange = oninput, # For IE11 support
"aria-label" = sprintf("Filter by minimum %s", name)
)
}
)
),
defaultPageSize = 5,
elementId = "cars-range"
)
External range filter
This example shows how you can create custom filter inputs outside the table, using a more complex version of the range filter from above.
library(htmltools)
# Custom range input filter with label and value
rangeFilter <- function(tableId, columnId, label, min, max, value = NULL, step = NULL, width = "200px") {
value <- if (!is.null(value)) value else min
inputId <- sprintf("filter_%s_%s", tableId, columnId)
valueId <- sprintf("filter_%s_%s__value", tableId, columnId)
oninput <- paste(
sprintf("document.getElementById('%s').textContent = this.value;", valueId),
sprintf("Reactable.setFilter('%s', '%s', this.value)", tableId, columnId)
)
div(
tags$label(`for` = inputId, label),
div(
style = sprintf("display: flex; align-items: center; width: %s", validateCssUnit(width)),
tags$input(
id = inputId,
type = "range",
min = min,
max = max,
step = step,
value = value,
oninput = oninput,
onchange = oninput, # For IE11 support
style = "width: 100%;"
),
span(id = valueId, style = "margin-left: 8px;", value)
)
)
}
# Filter method that filters numeric columns by minimum value
filterMinValue <- JS("function(rows, columnId, filterValue) {
return rows.filter(function(row) {
return row.values[columnId] >= filterValue
})
}")
data <- MASS::Cars93[, c("Manufacturer", "Model", "Type", "Price")]
browsable(tagList(
rangeFilter(
"cars-ext-range",
"Price",
"Filter by Minimum Price",
floor(min(data$Price)),
ceiling(max(data$Price))
),
reactable(
data,
columns = list(
Price = colDef(filterMethod = filterMinValue)
),
defaultPageSize = 5,
elementId = "cars-ext-range"
)
))
External checkbox filter
This example shows how you can filter a column with logical values using an external checkbox input.
The data contains a lot of missing values, and we want a checkbox
that allows you to show just the rows with missing values. So we add a
hidden column that indicates with TRUE
or
FALSE
whether any values in the row are missing, and filter
for true
values when the checkbox is checked.
library(htmltools)
data <- MASS::Cars93[, c("Manufacturer", "Model", "Type", "Price")]
# Add missing values to the data
set.seed(123)
data[] <- lapply(data, function(x) {
x[sample(1:length(x), length(x) / 3)] <- NA
x
})
# Indicates with TRUE or FALSE whether any values in the row are missing
data$has_missing <- !complete.cases(data)
browsable(tagList(
tags$label(
tags$input(
type = "checkbox",
onclick = "Reactable.setFilter('cars-missing', 'has_missing', event.target.checked)"
),
"Show missing values only"
),
reactable(
data,
columns = list(
# Hidden column for filtering missing values
has_missing = colDef(
show = FALSE,
filterMethod = JS("function(rows, columnId, filterValue) {
if (filterValue === true) {
return rows.filter(function(row) {
const hasMissing = row.values[columnId]
return hasMissing
})
}
return rows
}")
)
),
defaultColDef = colDef(na = "-"),
defaultPageSize = 5,
elementId = "cars-missing"
)
))
Basic custom filter input (React)
This example shows how you could write your custom filter input
entirely in JavaScript, using React. It renders a basic text input
filter for the Manufacturer
column.
data <- MASS::Cars93[, c("Manufacturer", "Model", "Type", "Price")]
reactable(
data,
columns = list(
Manufacturer = colDef(
filterable = TRUE,
filterInput = JS("function(column) {
return React.createElement('input', {
type: 'text',
value: column.filterValue,
onChange: function(event) {
// Set to undefined to clear the filter
return column.setFilter(event.target.value || undefined)
},
'aria-label': 'Filter ' + column.name,
style: { width: '100%' }
})
}")
)
),
defaultPageSize = 5
)
Range filter (React)
Here’s a more complex example of a React-based filter input, with
filter values that depend on the column data. The min and max values of
the column are found dynamically from state.data
.
The JavaScript code is embedded as a separate js
language chunk to make it easier to work with. but it could also be
included through an external JavaScript file, or inlined in R using
htmltools::tags$script()
.
// Custom range filter with value label
function rangeFilter(column, state) {
// Get min and max values from raw table data
let min = Infinity
let max = 0
state.data.forEach(function(row) {
const value = row[column.id]
if (value < min) {
min = Math.floor(value)
} else if (value > max) {
max = Math.ceil(value)
}
})
const filterValue = column.filterValue || min
const input = React.createElement('input', {
type: 'range',
value: filterValue,
min: min,
max: max,
onChange: function(event) {
// Set to undefined to clear the filter
column.setFilter(event.target.value || undefined)
},
style: { width: '100%', marginRight: '8px' },
'aria-label': 'Filter ' + column.name
})
return React.createElement(
'div',
{ style: { display: 'flex', alignItems: 'center', height: '100%' } },
[input, filterValue]
)
}
// Filter method that filters numeric columns by minimum value
function filterMinValue(rows, columnId, filterValue) {
return rows.filter(function(row) {
return row.values[columnId] >= filterValue
})
}