Server-Side Data
Why server-side data?
By default, DataGrid() sends all rows
to the browser at once. Pagination, sorting, and filtering are handled
entirely in JavaScript on the client side. This works well for small
datasets, but becomes a problem with large ones:
- Performance: sending thousands of rows as JSON slows down the initial page load.
- Memory: the browser must hold the entire dataset in memory.
- Security: all data is accessible in the browser, even rows not currently displayed.
With server-side data, only the rows for the current page are sent to the browser. Pagination, sorting, and filtering are handled in R on the server. This keeps the app fast regardless of dataset size.
Client-side vs server-side
DataGrid() (client-side) |
DataGridServer() (server-side) |
|
|---|---|---|
| Data sent to browser | All rows at once | Current page only |
| Pagination | JavaScript | R server |
| Sorting | JavaScript | R server |
| Filtering | JavaScript | R server |
| Requires Shiny | No | Yes |
How it works
DataGridServer() is a Shiny-only component. It uses a
custom React component that:
- Manages pagination, sort, and filter state internally in the browser.
- Sends the current state to R via
input$<inputId>whenever the user changes page, sorts a column, or applies a filter. - R processes the data and sends back only the matching rows for the current page.
Basic usage
A minimal server-side DataGrid app requires three things:
-
reactOutput()in the UI -
processGridParams()to handle pagination, sorting, and filtering -
DataGridServer()to render the grid with the current page of data
library(shiny)
library(muiDataGrid)
library(muiMaterial)
library(dplyr)
all_data <- dplyr::starwars |>
select(name, height, mass, birth_year, gender, homeworld)
ui <- muiMaterialPage(
reactOutput("grid")
)
server <- function(input, output, session) {
output$grid <- renderReact({
result <- processGridParams(all_data, input$grid_params)
DataGridServer(
inputId = "grid_params",
rows = result$rows,
rowCount = result$rowCount,
initialPageSize = 10L,
pageSizeOptions = c(5L, 10L, 25L),
sx = list(height = 500)
)
})
}
shinyApp(ui, server)Key functions
DataGridServer()
Renders a DataGrid with server-side pagination, sorting, and filtering.
| Parameter | Description |
|---|---|
inputId |
Shiny input ID. Grid state is available as
input$<inputId>. |
rows |
Data frame with the rows for the current page only. |
columns |
Column definitions (auto-generated from rows if
NULL). |
rowCount |
Total number of matching rows across all pages. |
initialPageSize |
Initial number of rows per page (default 25). |
pageSizeOptions |
Available page size choices (default
c(10, 25, 50, 100)). |
loading |
Show a loading indicator (default FALSE). |
... |
Additional props passed to the MUI DataGrid. |
processGridParams()
Applies pagination, sorting, and filtering to a data frame based on
input$<inputId>.
| Parameter | Description |
|---|---|
data |
The full data frame. |
params |
input$<inputId> from DataGridServer
(NULL on first render). |
pageSize |
Default page size before first interaction (default 10). |
Returns a list with:
-
rows: the data frame for the current page -
rowCount: total number of matching rows (after filtering, before pagination)
Custom columns
You can define custom columns the same way as with
DataGrid():
server <- function(input, output, session) {
output$grid <- renderReact({
result <- processGridParams(all_data, input$grid_params)
DataGridServer(
inputId = "grid_params",
rows = result$rows,
rowCount = result$rowCount,
columns = list(
list(field = "name", headerName = "Name", flex = 1),
list(field = "height", headerName = "Height (cm)", type = "number", width = 120),
list(field = "mass", headerName = "Mass (kg)", type = "number", width = 120),
list(field = "gender", headerName = "Gender", width = 120)
),
initialPageSize = 10L
)
})
}Reading grid state
The grid state is available in input$<inputId> as
a list with three elements:
-
pagination_model: list withpage(0-indexed) andpageSize -
sort_model: list of sort items, each withfieldandsort(“asc” or “desc”) -
filter_model: list withitems, each containingfield,operator, andvalue
You can use this to display diagnostics or trigger other reactive logic:
server <- function(input, output, session) {
output$grid <- renderReact({
result <- processGridParams(all_data, input$grid_params)
DataGridServer("grid_params", rows = result$rows, rowCount = result$rowCount)
})
observe({
params <- input$grid_params
if (!is.null(params)) {
cat("Page:", params$pagination_model$page, "\n")
cat("Page size:", params$pagination_model$pageSize, "\n")
}
})
}Custom server-side logic
processGridParams() covers common sorting and filtering
operators. If you need custom logic (e.g. querying a database), you can
skip it and handle input$<inputId> directly:
server <- function(input, output, session) {
output$grid <- renderReact({
params <- input$grid_params
page <- if (!is.null(params)) params$pagination_model$page else 0
page_size <- if (!is.null(params)) params$pagination_model$pageSize else 10
# Custom database query
result <- DBI::dbGetQuery(con, sprintf(
"SELECT * FROM my_table ORDER BY id LIMIT %d OFFSET %d",
page_size, page * page_size
))
total <- DBI::dbGetQuery(con, "SELECT COUNT(*) FROM my_table")[[1]]
DataGridServer(
inputId = "grid_params",
rows = result,
rowCount = total,
initialPageSize = 10L
)
})
}