Skip to content
+

Data Grid - Server-side data

The data grid server-side data.

Introduction

Server-side data management in React can become complex with growing datasets. Challenges include manual data fetching, pagination, sorting, filtering, and performance optimization. A dedicated module can help abstract these complexities, improving user experience.

Consider a Data Grid displaying a list of users. It supports pagination, sorting by column headers, and filtering. The Data Grid fetches data from the server when the user changes the page or updates filtering or sorting.

const [rows, setRows] = React.useState([]);
const [paginationModel, setPaginationModel] = React.useState({
  page: 0,
  pageSize: 10,
});
const [filterModel, setFilterModel] = React.useState({ items: [] });
const [sortModel, setSortModel] = React.useState([]);

React.useEffect(() => {
  const fetcher = async () => {
    // fetch data from server
    const data = await fetch('https://my-api.com/data', {
      method: 'GET',
      body: JSON.stringify({
        page: paginationModel.page,
        pageSize: paginationModel.pageSize,
        sortModel,
        filterModel,
      }),
    });
    setRows(data.rows);
  };
  fetcher();
}, [paginationModel, sortModel, filterModel]);

<DataGridPro
  columns={columns}
  pagination
  sortingMode="server"
  filterMode="server"
  paginationMode="server"
  onPaginationModelChange={setPaginationModel}
  onSortModelChange={setSortModel}
  onFilterModelChange={setFilterModel}
/>;

This example only scratches the surface with a lot of problems still unsolved like:

  • Performance optimization
  • Caching data/deduping requests
  • More complex use-cases on the server like grouping, tree data, etc.
  • Server-side row editing
  • Lazy loading of data
  • Handling updates to the data like row editing, row deletion, etc.
  • Refetching data on-demand

Trying to solve these problems one after the other can make the code complex and hard to maintain.

Data source

The idea for a centralized data source is to simplify server-side data fetching. It's an abstraction layer between the Data Grid and the server, providing a simple interface for interacting with the server. Think of it like a middleman handling the communication between the Data Grid (client) and the actual data source (server).

It has an initial set of required methods that you need to implement. The data grid will use these methods internally to fetch a sub-set of data when needed.

Let's take a look at the minimal GridDataSource interface configuration.

interface GridDataSource {
  /**
   * This method will be called when the grid needs to fetch some rows
   * @param {GridGetRowsParams} params The parameters required to fetch the rows
   * @returns {Promise<GridGetRowsResponse>} A promise that resolves to the data of type [GridGetRowsResponse]
   */
  getRows(params: GridGetRowsParams): Promise<GridGetRowsResponse>;
}

Here's how the above mentioned example would look like when implemented with the data source:

const customDataSource: GridDataSource = {
  getRows: async (params: GridGetRowsParams): GetRowsResponse => {
    const response = await fetch('https://my-api.com/data', {
      method: 'GET',
      body: JSON.stringify(params),
    });
    const data = await response.json();

    return {
      rows: data.rows,
      rowCount: data.totalCount,
    };
  },
}

<DataGridPro
  columns={columns}
  unstable_dataSource={customDataSource}
  pagination
/>

The code has been significantly reduced, the need for managing the controlled states is removed, and data fetching logic is centralized.

Server-side filtering, sorting, and pagination

The data source changes how the existing server-side features like filtering, sorting, and pagination work.

Without data source

When there's no data source, the features filtering, sorting, pagination work on client by default. In order for them to work with server-side data, you need to set them to server explicitly and provide the onFilterModelChange, onSortModelChange, onPaginationModelChange event handlers to fetch the data from the server based on the updated variables.

<DataGrid
  columns={columns}
  rows={rows}
  pagination
  sortingMode="server"
  filterMode="server"
  paginationMode="server"
  onPaginationModelChange={(newPaginationModel) => {
    // fetch data from server
  }}
  onSortModelChange={(newSortModel) => {
    // fetch data from server
  }}
  onFilterModelChange={(newFilterModel) => {
    // fetch data from server
  }}
/>

With data source

With the data source, the features filtering, sorting, pagination are automatically set to server.

When the corresponding models update, the data grid calls the getRows method with the updated values of type GridGetRowsParams to get updated data.

<DataGridPro
  columns={columns}
  unstable_dataSource={customDataSource} // automatically sets `sortingMode="server"`, `filterMode="server"`, `paginationMode="server"`
/>

The following demo showcases this behavior.

Data caching

The data source caches fetched data by default. This means that if the user navigates to a page or expands a node that has already been fetched, the grid will not call the getRows function again to avoid unnecessary calls to the server.

The GridDataSourceCacheDefault is used by default which is a simple in-memory cache that stores the data in a plain object. It could be seen in action in the demo below.

Customize the cache lifetime

The GridDataSourceCacheDefault has a default Time To Live (ttl) of 5 minutes. To customize it, pass the ttl option in milliseconds to the GridDataSourceCacheDefault constructor, and then pass it as the unstable_dataSourceCache prop.

import { GridDataSourceCacheDefault } from '@mui/x-data-grid-pro';

const lowTTLCache = new GridDataSourceCacheDefault({ ttl: 1000 * 10 }); // 10 seconds

<DataGridPro
  columns={columns}
  unstable_dataSource={customDataSource}
  unstable_dataSourceCache={lowTTLCache}
/>;

Custom cache

To provide a custom cache, use unstable_dataSourceCache prop, which could be either written from scratch or based out of another cache library. This prop accepts a generic interface of type GridDataSourceCache.

export interface GridDataSourceCache {
  set: (key: GridGetRowsParams, value: GridGetRowsResponse) => void;
  get: (key: GridGetRowsParams) => GridGetRowsResponse | undefined;
  clear: () => void;
}

Disable cache

To disable the data source cache, pass null to the unstable_dataSourceCache prop.

<DataGridPro
  columns={columns}
  unstable_dataSource={customDataSource}
  unstable_dataSourceCache={null}
/>
Press Enter to start editing

Error handling

You could handle the errors with the data source by providing an error handler function using the unstable_onDataSourceError. It will be called whenever there's an error in fetching the data.

The first argument of this function is the error object, and the second argument is the fetch parameters of type GridGetRowsParams.

<DataGridPro
  columns={columns}
  unstable_dataSource={customDataSource}
  unstable_onDataSourceError={(error, params) => {
    console.error(error);
  }}
/>

Updating data 🚧

This feature is yet to be implemented, when completed, the method unstable_dataSource.updateRow will be called with the GridRowModel whenever the user edits a row. It will work in a similar way as the processRowUpdate prop.

Feel free to upvote the related GitHub issue to see this feature land faster.

API