From 0dba87eb6b7215b42718fc78e0e43b540336eb64 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Mon, 2 Oct 2023 15:28:22 +0500 Subject: [PATCH 1/7] [DataGridPro] Datasource interface --- docs/data/data-grid/server-side/datasource.md | 187 ++++++++++++++++++ docs/pages/x/react-data-grid/datasource.js | 7 + .../x-data-grid-pro/src/models/dataSource.ts | 60 ++++++ 3 files changed, 254 insertions(+) create mode 100644 docs/data/data-grid/server-side/datasource.md create mode 100644 docs/pages/x/react-data-grid/datasource.js create mode 100644 packages/grid/x-data-grid-pro/src/models/dataSource.ts diff --git a/docs/data/data-grid/server-side/datasource.md b/docs/data/data-grid/server-side/datasource.md new file mode 100644 index 000000000000..d09408e14d56 --- /dev/null +++ b/docs/data/data-grid/server-side/datasource.md @@ -0,0 +1,187 @@ +# Data Grid - Server-side data source + +

The data grid server-side data interface.

+ +:::warning + +This feature is still under development and the information shared on this page is subject to change. Feel free to subscribe or comment on the official GitHub issue. + +::: + +## Overview + +The Data Grid already supports server-side data fetching. In order to make it more powerful and simple to use, we provide a data source interface that you can implement with your existing data fetching logic. + +The datasource will work with all the major data grid features which require server-side data fetching out , such as sorting, filtering, pagination, and grouping. + +## Working + +The data grid server-side data source has an initial set of required methods that you need to implement. The data grid will call these methods when it needs to fetch data. + +```tsx +interface DataSource { + /** + Fetcher Functions: + - `getRows` is required + - `updateRow` is optional + + `getRows` will be used by the grid to fetch data for the current page or children for the current parent group + It may return a `rowCount` to update the total count of rows in the grid + */ + getRows(params: GetRowsParams): Promise; + // if passed, will be called like `processRowUpdate` on row edit + updateRow?(updatedRow: GridRowModel): Promise; +} +``` + +Here's how you can configure the data grid to use your data source with most basic configuration: + +```tsx +const myCustomDataSource = { + getRows: async (params: GetRowsParams): GetRowsResponse => { + // fetch data from server + const response = await fetch('https://my-api.com/data', { + method: 'GET', + body: JSON.stringify(params), + }); + const data = await response.json(); + // return the data and the total number of rows + return { + rows: data.rows, + rowCount: data.totalCount, + }; + }, +} + + +``` + +### Loading data + +The method `dataSource.getRows` will be called with the `GetRowsParams` object whenever some data from the server is needed. This object contains all the information that you need to fetch the data from the server. + +Since previously, the data grid did not support internal data fetching, the `rows` prop was the way to pass the data to the grid. However, with server-side data, the `rows` prop is no longer needed. Instead, the data grid will call the `getRows` method whenever it needs to fetch data. + +Here's the `GetRowsParams` object for reference: + +```tsx +interface GetRowsParams { + sortModel: GridSortModel; + filterModel: GridFilterModel; + groupKeys: string[]; // array of keys returned by `getGroupKey` of all the parent rows until the row for which the data is requested + paginationModel: GridPaginationModel; // alternate to `start`, `end` + start: number | string; // first row index to fetch or cursor information + end: number; // last row index to fetch + groupFields: GridColDef['field'][]; // list of grouped columns (`rowGrouping`) +} +``` + +And here's the `GetRowsResponse` object for reference: + +```tsx +interface GetRowsResponse { + rows: GridRowModel[]; + rowCount?: number; // optional: to reflect updates in total `rowCount` + pageInfo?: { + hasNextPage?: boolean; // optional: when row count is unknown/inaccurate, if `truncated` is set or rowCount is not known, data will keep loading until `hasNextPage` is `false` + truncated?: number; // optional: to reflect rowCount is inaccurate (will trigger `x-y of many` in pagination) + }; +} +``` + +### Updating data + +If provided, the method `dataSource.updateRow` will be called with the `GridRowModel` object whenever the user edits a row. This method is optional and you can skip it if you don't need to update the data on the server. It will work in a similar way as the `processRowUpdate` prop. + +### Data grid props + +These data grid props will work with the server-side data source: + +- `dataSource: DataSource`: the data source object that you need to implement +- `queryClient: QueryClient`: optional: if not provided, a new instance of `QueryClient` will be created internally +- `rows`: will be ignored, could be skipped when `dataSource` is provided +- `rowCount`: will be used to identify the total number of rows in the grid, if not provided, the grid will use the _GetRowsResponse.rowCount_ value + +Props related to grouped data (`treeData` and `rowGrouping`): + +- `getGroupKey(row: GridRowModel): string` + + will be used by the grid to group rows by their parent group + This effectively replaces `getTreeDataPath`. + Consider this structure: + + ``` + - (1) Sarah // groupKey 'Sarah' + - (2) Thomas // groupKey 'Thomas' + ``` + + When (2) is expanded, the `getRows` function will be called with group keys `['Sarah', 'Thomas']`. + +- `hasChildren?(row: GridRowModel): boolean` + + Will be used by the grid to determine if a row has children on server + +- `getChildrenCount?: (row: GridRowModel) => number` + + Will be used by the grid to determine the number of children of a row on server + +### Existing server-side features + +The server-side `dataSource` will change a bit the way existing server-side features work. Whenever a valid data source is passed the features `filtering`, `sorting`, `pagination` will automatically be set to `server`. + +However, if for some reason, you want to explicitly work one or more of these features to work on `client`, you can do so by passing the `client` value to the feature. Here's an example: + +```tsx + +``` + +### Caching + +The data grid will cache the data it receives from the server. This means that if the user navigates to a page that has already been fetched, the grid will not call the `getRows` function again. This is to avoid unnecessary calls to the server. + +By default, the data grid will use the tanstack react-query library to cache the data. A `QueryClient` is created internally and used with the data source. You can also pass your own instance of `QueryClient` in order to have access to the cached data outside the data grid. You can also use this client to invalidate the cache when you need to. + +In order to access the current keys for the `QueryClient`, you can wrap the application into the `DataSourceProvider`. + +```tsx + + + + +``` + +Or you could use the `onCacheUpdate` event to listen whenever grid populates the cache with new data. + +```tsx + { + // params will contain the `QueryKey` + // you can use this to access or invalidate the cache + myCustomQueryClientInstance.getQueryData(params.queryKey); + }} +/> +``` + +## API + +- [DataGrid](/x/api/data-grid/data-grid/) +- [DataGridPro](/x/api/data-grid/data-grid-pro/) +- [DataGridPremium](/x/api/data-grid/data-grid-premium/) diff --git a/docs/pages/x/react-data-grid/datasource.js b/docs/pages/x/react-data-grid/datasource.js new file mode 100644 index 000000000000..72c720eb1947 --- /dev/null +++ b/docs/pages/x/react-data-grid/datasource.js @@ -0,0 +1,7 @@ +import * as React from 'react'; +import MarkdownDocs from 'docs/src/modules/components/MarkdownDocs'; +import * as pageProps from 'docsx/data/data-grid/server-side/datasource.md?@mui/markdown'; + +export default function Page() { + return ; +} diff --git a/packages/grid/x-data-grid-pro/src/models/dataSource.ts b/packages/grid/x-data-grid-pro/src/models/dataSource.ts new file mode 100644 index 000000000000..01b5ff748ea0 --- /dev/null +++ b/packages/grid/x-data-grid-pro/src/models/dataSource.ts @@ -0,0 +1,60 @@ +import { + GridSortModel, + GridFilterModel, + GridColDef, + GridRowModel, + GridPaginationModel, +} from '@mui/x-data-grid'; + +interface GetRowsParams { + sortModel: GridSortModel; + filterModel: GridFilterModel; + groupKeys: string[]; // array of keys returned by `getGroupKey` of all the parent rows until the row for which the data is requested + paginationModel: GridPaginationModel; // alternate to `start`, `end` + start: number | string; // first row index to fetch or cursor information + end: number; // last row index to fetch + groupFields: GridColDef['field'][]; // list of grouped columns (`rowGrouping`) +} + +interface GetRowsResponse { + rows: GridRowModel[]; + rowCount?: number; // optional: to reflect updates in total `rowCount` + pageInfo?: { + hasNextPage?: boolean; // optional: when row count is unknown/inaccurate, if `truncated` is set or rowCount is not known, data will keep loading until `hasNextPage` is `false` + truncated?: number; // optional: to reflect rowCount is inaccurate (will trigger `x-y of many` in pagination) + }; +} + +export interface DataSource { + /** + Fetcher Functions: + - `getRows` is required + - `updateRow` is optional + + `getRows` will be used by the grid to fetch data for the current page or children for the current parent group + It may return a `rowCount` to update the total count of rows in the grid + */ + getRows(params: GetRowsParams): Promise; + // if passed, will be called like `processRowUpdate` on row edit + updateRow?(rows: GridRowModel): Promise; + + /** + Functions related to grouped data (`treeData` and `rowGrouping`): + - `getGroupKey` is required + - `hasChildren` is required + - `getChildrenCount` is optional + + `getGroupKey` will be used by the grid to group rows by their parent group + This effectively replaces `getTreeDataPath`. + Consider this structure: + - (1) Sarah // groupKey 'Sarah' + - (2) Thomas // groupKey 'Thomas' + When (2) is expanded, the `load` function will be called with group keys ['Sarah', 'Thomas']. + `hasChildren` will be used by the grid to determine if a row has children on server + `getChildrenCount` will be used by the grid to determine the number of children of a row on server + */ + getGroupKey(row: GridRowModel): string; // returns the key of the group to which the row belongs (`treeData` and `rowGrouping`) + hasChildren(row: GridRowModel): boolean; // return `true` if row has children on server + getChildrenCount?: (row: GridRowModel) => number; // optional child count + // these /\ 3 could also be root props of the grid +} From b3ed72078174c72328b539c65ce362c9e37c9dfc Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Thu, 5 Oct 2023 17:30:13 +0500 Subject: [PATCH 2/7] Improve a bit --- .../index.md} | 112 +++++++++++++++--- docs/data/pages.ts | 5 + .../index.js} | 2 +- 3 files changed, 103 insertions(+), 16 deletions(-) rename docs/data/data-grid/{server-side/datasource.md => server-side-data/index.md} (60%) rename docs/pages/x/react-data-grid/{datasource.js => server-side-data/index.js} (66%) diff --git a/docs/data/data-grid/server-side/datasource.md b/docs/data/data-grid/server-side-data/index.md similarity index 60% rename from docs/data/data-grid/server-side/datasource.md rename to docs/data/data-grid/server-side-data/index.md index d09408e14d56..a25da14a3896 100644 --- a/docs/data/data-grid/server-side/datasource.md +++ b/docs/data/data-grid/server-side-data/index.md @@ -1,6 +1,81 @@ -# Data Grid - Server-side data source +# Data Grid - Server-side data -

The data grid server-side data interface.

+

The data grid server-side data

+ +## Overview + +Managing server-side data efficiently in a React application can become complex as the dataset grows. + +Without a dedicated module that abstracts it's complexities, developers often face challenges related to manual data fetching, pagination, sorting, filtering, and it's often gets trickier to tackle performance issues, which can lead to a poor user experience. + +Let's take a look at an example: + +### Example scenario + +Let's say you have a data grid that displays a list of users. The data grid has pagination enabled and the user can sort the data by clicking on the column headers and also apply filters. + +The data grid is configured to fetch data from the server whenever the user changes the page or updates filtering or sorting. + +```tsx +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(); +}, [page, sortModel, filterModel]); + + { + setPaginationModel(newPaginationModel); + }} + onSortModelChange={(newSortModel) => { + setSortModel(newSortModel); + }} + onFilterModelChange={(newFilterModel) => { + setFilterModel(newFilterModel); + }} +/>; +``` + +We only scratched the surface in the above example 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. + +Trying to solve these problems one after the other can make the code complex and hard to maintain. + +## Data source + +A very common pattern to solve these problems is to use a centralized data source. A data source is an abstraction layer that sits between the data grid and the server. It provides a simple interface to the data grid to fetch data and update it. It handles a lot of the complexities related to server side data fetching. We will talk more about the data source in the next section. :::warning @@ -8,15 +83,15 @@ This feature is still under development and the information shared on thi ::: -## Overview +### Overview -The Data Grid already supports server-side data fetching. In order to make it more powerful and simple to use, we provide a data source interface that you can implement with your existing data fetching logic. +The Data Grid already supports manual server-side data fetching for features like sorting, filtering, etc. In order to make it more powerful and simple to use, we provide a data source interface that you can implement with your existing data fetching logic. -The datasource will work with all the major data grid features which require server-side data fetching out , such as sorting, filtering, pagination, and grouping. +The datasource will work with all the major data grid features which require server-side data fetching such as sorting, filtering, pagination, grouping, etc. -## Working +### Usage -The data grid server-side data source has an initial set of required methods that you need to implement. The data grid will call these methods when it needs to fetch data. +The data grid server-side data source has an initial set of required methods that you need to implement. The data grid will call these methods internally when the data is required for a specific page. ```tsx interface DataSource { @@ -34,7 +109,7 @@ interface DataSource { } ``` -Here's how you can configure the data grid to use your data source with most basic configuration: +Here's how the code will look like for the above example: ```tsx const myCustomDataSource = { @@ -56,10 +131,15 @@ const myCustomDataSource = { ``` -### Loading data +Not only the code has been reduced to less than 50%, it has removed the hassle of managing controlled states and data fetching logic. + +On top of that, the data source will also handle a lot of other aspects like caching and deduping of requests. + +#### Loading data The method `dataSource.getRows` will be called with the `GetRowsParams` object whenever some data from the server is needed. This object contains all the information that you need to fetch the data from the server. @@ -92,11 +172,11 @@ interface GetRowsResponse { } ``` -### Updating data +#### Updating data If provided, the method `dataSource.updateRow` will be called with the `GridRowModel` object whenever the user edits a row. This method is optional and you can skip it if you don't need to update the data on the server. It will work in a similar way as the `processRowUpdate` prop. -### Data grid props +#### Data grid props These data grid props will work with the server-side data source: @@ -128,7 +208,7 @@ Props related to grouped data (`treeData` and `rowGrouping`): Will be used by the grid to determine the number of children of a row on server -### Existing server-side features +#### Existing server-side features The server-side `dataSource` will change a bit the way existing server-side features work. Whenever a valid data source is passed the features `filtering`, `sorting`, `pagination` will automatically be set to `server`. @@ -142,13 +222,15 @@ However, if for some reason, you want to explicitly work one or more of these fe /> ``` -### Caching +#### Caching The data grid will cache the data it receives from the server. This means that if the user navigates to a page that has already been fetched, the grid will not call the `getRows` function again. This is to avoid unnecessary calls to the server. -By default, the data grid will use the tanstack react-query library to cache the data. A `QueryClient` is created internally and used with the data source. You can also pass your own instance of `QueryClient` in order to have access to the cached data outside the data grid. You can also use this client to invalidate the cache when you need to. +By default, the data grid will use the tanstack query library to cache the data. A `QueryClient` will be created internally and used with the data source. You can also pass your own instance of `QueryClient` with a optional custom configuration and use it to play with the cached data. You can also use this client to invalidate the cache when you need to. + +**Cache key sharing**: -In order to access the current keys for the `QueryClient`, you can wrap the application into the `DataSourceProvider`. +In order to subscribe to the new additions of the keys to the `QueryClient`, you can wrap the application into the `DataSourceProvider`. ```tsx diff --git a/docs/data/pages.ts b/docs/data/pages.ts index 2d904f17cf5f..eec89d14ac49 100644 --- a/docs/data/pages.ts +++ b/docs/data/pages.ts @@ -93,6 +93,11 @@ const pages: MuiPage[] = [ { pathname: '/x/react-data-grid/virtualization' }, { pathname: '/x/react-data-grid/accessibility' }, { pathname: '/x/react-data-grid/performance' }, + { + pathname: '/x/react-data-grid/server-side-data', + title: 'Server-side data 🚧', + plan: 'pro', + }, { pathname: '/x/react-data-grid-group-pivot', title: 'Group & Pivot', diff --git a/docs/pages/x/react-data-grid/datasource.js b/docs/pages/x/react-data-grid/server-side-data/index.js similarity index 66% rename from docs/pages/x/react-data-grid/datasource.js rename to docs/pages/x/react-data-grid/server-side-data/index.js index 72c720eb1947..8d183d6d499c 100644 --- a/docs/pages/x/react-data-grid/datasource.js +++ b/docs/pages/x/react-data-grid/server-side-data/index.js @@ -1,6 +1,6 @@ import * as React from 'react'; import MarkdownDocs from 'docs/src/modules/components/MarkdownDocs'; -import * as pageProps from 'docsx/data/data-grid/server-side/datasource.md?@mui/markdown'; +import * as pageProps from 'docsx/data/data-grid/server-side-data/index.md?@mui/markdown'; export default function Page() { return ; From fa3f3fcd27f95b3fd93b1620ad750f8679bce442 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Wed, 18 Oct 2023 17:10:43 +0500 Subject: [PATCH 3/7] Update a bit the documentation --- docs/data/data-grid/server-side-data/index.md | 87 ++++++++----------- 1 file changed, 35 insertions(+), 52 deletions(-) diff --git a/docs/data/data-grid/server-side-data/index.md b/docs/data/data-grid/server-side-data/index.md index a25da14a3896..26c0ca1c0700 100644 --- a/docs/data/data-grid/server-side-data/index.md +++ b/docs/data/data-grid/server-side-data/index.md @@ -8,11 +8,11 @@ Managing server-side data efficiently in a React application can become complex Without a dedicated module that abstracts it's complexities, developers often face challenges related to manual data fetching, pagination, sorting, filtering, and it's often gets trickier to tackle performance issues, which can lead to a poor user experience. -Let's take a look at an example: +Have a look at an example: ### Example scenario -Let's say you have a data grid that displays a list of users. The data grid has pagination enabled and the user can sort the data by clicking on the column headers and also apply filters. +Imagine having a data grid that displays a list of users. The data grid has pagination enabled and the user can sort the data by clicking on the column headers and also apply filters. The data grid is configured to fetch data from the server whenever the user changes the page or updates filtering or sorting. @@ -50,15 +50,9 @@ React.useEffect(() => { sortingMode="server" filterMode="server" paginationMode="server" - onPaginationModelChange={(newPaginationModel) => { - setPaginationModel(newPaginationModel); - }} - onSortModelChange={(newSortModel) => { - setSortModel(newSortModel); - }} - onFilterModelChange={(newFilterModel) => { - setFilterModel(newFilterModel); - }} + onPaginationModelChange={setPaginationModel} + onSortModelChange={setSortModel} + onFilterModelChange={setFilterModel} />; ``` @@ -70,6 +64,7 @@ We only scratched the surface in the above example with a lot of problems still - 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. @@ -112,7 +107,7 @@ interface DataSource { Here's how the code will look like for the above example: ```tsx -const myCustomDataSource = { +const customDataSource: DataSource = { getRows: async (params: GetRowsParams): GetRowsResponse => { // fetch data from server const response = await fetch('https://my-api.com/data', { @@ -130,12 +125,12 @@ const myCustomDataSource = { ``` -Not only the code has been reduced to less than 50%, it has removed the hassle of managing controlled states and data fetching logic. +Not only the code has been reduced significantly, it has removed the hassle of managing controlled states and data fetching logic too. On top of that, the data source will also handle a lot of other aspects like caching and deduping of requests. @@ -181,7 +176,6 @@ If provided, the method `dataSource.updateRow` will be called with the `GridRowM These data grid props will work with the server-side data source: - `dataSource: DataSource`: the data source object that you need to implement -- `queryClient: QueryClient`: optional: if not provided, a new instance of `QueryClient` will be created internally - `rows`: will be ignored, could be skipped when `dataSource` is provided - `rowCount`: will be used to identify the total number of rows in the grid, if not provided, the grid will use the _GetRowsResponse.rowCount_ value @@ -210,58 +204,47 @@ Props related to grouped data (`treeData` and `rowGrouping`): #### Existing server-side features -The server-side `dataSource` will change a bit the way existing server-side features work. Whenever a valid data source is passed the features `filtering`, `sorting`, `pagination` will automatically be set to `server`. +The server-side `dataSource` will change a bit the way existing server-side features work. -However, if for some reason, you want to explicitly work one or more of these features to work on `client`, you can do so by passing the `client` value to the feature. Here's an example: +**Without data source**: +When there's no data source, the features `filtering`, `sorting`, `pagination` will work on `client` by default. In order for them to work with server-side data, you need to set them to `server` explicitly and listen to the `onFilterModelChange`, `onSortModelChange`, `onPaginationModelChange` events to fetch the data from the server based on the updated variables. ```tsx - { + // fetch data from server + }} + onSortModelChange={(newSortModel) => { + // fetch data from server + }} + onFilterModelChange={(newFilterModel) => { + // fetch data from server + }} /> ``` -#### Caching +**With data source**: +However, with a valid data source passed the features `filtering`, `sorting`, `pagination` will automatically be set to `server`. -The data grid will cache the data it receives from the server. This means that if the user navigates to a page that has already been fetched, the grid will not call the `getRows` function again. This is to avoid unnecessary calls to the server. - -By default, the data grid will use the tanstack query library to cache the data. A `QueryClient` will be created internally and used with the data source. You can also pass your own instance of `QueryClient` with a optional custom configuration and use it to play with the cached data. You can also use this client to invalidate the cache when you need to. - -**Cache key sharing**: - -In order to subscribe to the new additions of the keys to the `QueryClient`, you can wrap the application into the `DataSourceProvider`. - -```tsx - - - - -``` - -Or you could use the `onCacheUpdate` event to listen whenever grid populates the cache with new data. +You just need to implement the `getRows` method and the data grid will call the `getRows` method with the proper params whenever it needs data. ```tsx { - // params will contain the `QueryKey` - // you can use this to access or invalidate the cache - myCustomQueryClientInstance.getQueryData(params.queryKey); - }} + dataSource={customDataSource} // this automatically means `sortingMode="server"`, `filterMode="server"`, `paginationMode="server"` /> ``` +#### Caching + +The data grid will cache the data it receives from the server. This means that if the user navigates to a page that has already been fetched, the grid will not call the `getRows` function again. This is to avoid unnecessary calls to the server. + ## API - [DataGrid](/x/api/data-grid/data-grid/) From 24e6a2a1824eae922c261d98b0d4a8ff6ee01b34 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Wed, 1 Nov 2023 15:47:16 +0500 Subject: [PATCH 4/7] Add more pages and refinement --- .../data-grid/server-side-data/aggregation.md | 15 +++++ docs/data/data-grid/server-side-data/index.md | 59 ++++++++++++++--- .../server-side-data/infinite-loading.md | 15 +++++ .../server-side-data/lazy-loading.md | 15 +++++ .../server-side-data/row-grouping.md | 15 +++++ .../data-grid/server-side-data/tree-data.md | 15 +++++ docs/data/pages.ts | 38 +++++++++-- .../server-side-data/aggregation.js | 7 ++ .../server-side-data/infinite-loading.js | 7 ++ .../server-side-data/lazy-loading.js | 7 ++ .../server-side-data/row-grouping.js | 7 ++ .../server-side-data/tree-data.js | 7 ++ .../x-data-grid-pro/src/models/dataSource.ts | 66 +++++++++++-------- 13 files changed, 231 insertions(+), 42 deletions(-) create mode 100644 docs/data/data-grid/server-side-data/aggregation.md create mode 100644 docs/data/data-grid/server-side-data/infinite-loading.md create mode 100644 docs/data/data-grid/server-side-data/lazy-loading.md create mode 100644 docs/data/data-grid/server-side-data/row-grouping.md create mode 100644 docs/data/data-grid/server-side-data/tree-data.md create mode 100644 docs/pages/x/react-data-grid/server-side-data/aggregation.js create mode 100644 docs/pages/x/react-data-grid/server-side-data/infinite-loading.js create mode 100644 docs/pages/x/react-data-grid/server-side-data/lazy-loading.js create mode 100644 docs/pages/x/react-data-grid/server-side-data/row-grouping.js create mode 100644 docs/pages/x/react-data-grid/server-side-data/tree-data.js diff --git a/docs/data/data-grid/server-side-data/aggregation.md b/docs/data/data-grid/server-side-data/aggregation.md new file mode 100644 index 000000000000..3836a25e9ed5 --- /dev/null +++ b/docs/data/data-grid/server-side-data/aggregation.md @@ -0,0 +1,15 @@ +--- +title: React Server-side row grouping +--- + +# Data Grid - Server-side aggregation 🚧 + +

Aggregation with server side data source.

+ +:::warning +This feature isn't implemented yet. It's coming. + +👍 Upvote [issue #10860](https://github.com/mui/mui-x/issues/10860) if you want to see it land faster. + +Don't hesitate to leave a comment on the same issue to influence what gets built. Especially if you already have a use case for this component, or if you are facing a pain point with your current solution. +::: diff --git a/docs/data/data-grid/server-side-data/index.md b/docs/data/data-grid/server-side-data/index.md index 26c0ca1c0700..b9e547934300 100644 --- a/docs/data/data-grid/server-side-data/index.md +++ b/docs/data/data-grid/server-side-data/index.md @@ -1,3 +1,7 @@ +--- +title: React Data Grid - Server-side data +--- + # Data Grid - Server-side data

The data grid server-side data

@@ -42,7 +46,7 @@ React.useEffect(() => { setRows(data.rows); }; fetcher(); -}, [page, sortModel, filterModel]); +}, [paginationModel, sortModel, filterModel]); under development and the information shared on this page is subject to change. Feel free to subscribe or comment on the official GitHub issue. +This feature is still under development and the information shared on this page is subject to change. Feel free to subscribe or comment on the official GitHub [issue](https://github.com/mui/mui-x/issues/8179). ::: @@ -99,7 +103,6 @@ interface DataSource { It may return a `rowCount` to update the total count of rows in the grid */ getRows(params: GetRowsParams): Promise; - // if passed, will be called like `processRowUpdate` on row edit updateRow?(updatedRow: GridRowModel): Promise; } ``` @@ -146,10 +149,27 @@ Here's the `GetRowsParams` object for reference: interface GetRowsParams { sortModel: GridSortModel; filterModel: GridFilterModel; - groupKeys: string[]; // array of keys returned by `getGroupKey` of all the parent rows until the row for which the data is requested - paginationModel: GridPaginationModel; // alternate to `start`, `end` + /** + * Alternate to `start` and `end`, maps to `GridPaginationModel` interface + */ + paginationModel: GridPaginationModel; + /** + * First row index to fetch (number) or cursor information (number | string) + */ start: number | string; // first row index to fetch or cursor information + /** + * Last row index to fetch + */ end: number; // last row index to fetch + /** + * Array of keys returned by `getGroupKey` of all the parent rows until the row for which the data is requested + * `getGroupKey` prop must be implemented to use this + * Useful for `treeData` and `rowGrouping` only + */ + groupKeys: string[]; + /** + * List of grouped columns (only applicable with `rowGrouping`) + */ groupFields: GridColDef['field'][]; // list of grouped columns (`rowGrouping`) } ``` @@ -158,11 +178,30 @@ And here's the `GetRowsResponse` object for reference: ```tsx interface GetRowsResponse { + /** + * Subset of the rows as per the passed `GetRowsParams` + */ rows: GridRowModel[]; - rowCount?: number; // optional: to reflect updates in total `rowCount` + /** + * To reflect updates in total `rowCount` (optional) + * Useful when the `rowCount` is inaccurate (e.g. when filtering) or not available upfront + */ + rowCount?: number; + /** + * Additional `pageInfo` to help the grid determine if there are more rows to fetch (corner-cases) + * `hasNextPage`: When row count is unknown/inaccurate, if `truncated` is set or rowCount is not known, data will keep loading until `hasNextPage` is `false` + * `truncated`: To reflect `rowCount` is inaccurate (will trigger `x-y of many` in pagination after the count of rows fetched is greater than provided `rowCount`) + * It could be useful with: + * 1. Cursor based pagination: + * When rowCount is not known, grid will check for `hasNextPage` to determine + * if there are more rows to fetch. + * 2. Inaccurate `rowCount`: + * `truncated: true` will let the grid know that `rowCount` is estimated/truncated. + * Thus `hasNextPage` will come into play to check more rows are available to fetch after the number becomes >= provided `rowCount` + */ pageInfo?: { - hasNextPage?: boolean; // optional: when row count is unknown/inaccurate, if `truncated` is set or rowCount is not known, data will keep loading until `hasNextPage` is `false` - truncated?: number; // optional: to reflect rowCount is inaccurate (will trigger `x-y of many` in pagination) + hasNextPage?: boolean; + truncated?: number; }; } ``` @@ -204,10 +243,10 @@ Props related to grouped data (`treeData` and `rowGrouping`): #### Existing server-side features -The server-side `dataSource` will change a bit the way existing server-side features work. +The server-side data source will change a bit the way existing server-side features like `filtering`, `sorting`, and `pagination` work. **Without data source**: -When there's no data source, the features `filtering`, `sorting`, `pagination` will work on `client` by default. In order for them to work with server-side data, you need to set them to `server` explicitly and listen to the `onFilterModelChange`, `onSortModelChange`, `onPaginationModelChange` events to fetch the data from the server based on the updated variables. +When there's no data source, the features `filtering`, `sorting`, `pagination` will work on `client` by default. In order for them to work with server-side data, you need to set them to `server` explicitly and listen to the [`onFilterModelChange`](https://mui.com/x/react-data-grid/filtering/server-side/), [`onSortModelChange`](https://mui.com/x/react-data-grid/sorting/#server-side-sorting), [`onPaginationModelChange`](https://mui.com/x/react-data-grid/pagination/#server-side-pagination) events to fetch the data from the server based on the updated variables. ```tsx Row infinite loading with server side data source.

+ +:::warning +This feature isn't implemented yet. It's coming. + +👍 Upvote [issue #10858](https://github.com/mui/mui-x/issues/10858) if you want to see it land faster. + +Don't hesitate to leave a comment on the same issue to influence what gets built. Especially if you already have a use case for this component, or if you are facing a pain point with the [current solution](https://mui.com/x/react-data-grid/row-updates/#infinite-loading). +::: diff --git a/docs/data/data-grid/server-side-data/lazy-loading.md b/docs/data/data-grid/server-side-data/lazy-loading.md new file mode 100644 index 000000000000..a8430737d67d --- /dev/null +++ b/docs/data/data-grid/server-side-data/lazy-loading.md @@ -0,0 +1,15 @@ +--- +title: React Server-side lazy loading +--- + +# Data Grid - Server-side lazy loading 🚧 + +

Row lazy-loading with server side data source.

+ +:::warning +This feature isn't implemented yet. It's coming. + +👍 Upvote [issue #10857](https://github.com/mui/mui-x/issues/10857) if you want to see it land faster. + +Don't hesitate to leave a comment on the same issue to influence what gets built. Especially if you already have a use case for this component, or if you are facing a pain point with the [current solution](https://mui.com/x/react-data-grid/row-updates/#lazy-loading). +::: diff --git a/docs/data/data-grid/server-side-data/row-grouping.md b/docs/data/data-grid/server-side-data/row-grouping.md new file mode 100644 index 000000000000..72fd21756bc2 --- /dev/null +++ b/docs/data/data-grid/server-side-data/row-grouping.md @@ -0,0 +1,15 @@ +--- +title: React Server-side row grouping +--- + +# Data Grid - Server-side row grouping 🚧 + +

Lazy-loaded row grouping with server side data source.

+ +:::warning +This feature isn't implemented yet. It's coming. + +👍 Upvote [issue #10859](https://github.com/mui/mui-x/issues/10859) if you want to see it land faster. + +Don't hesitate to leave a comment on the same issue to influence what gets built. Especially if you already have a use case for this component, or if you are facing a pain point with your current solution. +::: diff --git a/docs/data/data-grid/server-side-data/tree-data.md b/docs/data/data-grid/server-side-data/tree-data.md new file mode 100644 index 000000000000..ed69e9ac25d3 --- /dev/null +++ b/docs/data/data-grid/server-side-data/tree-data.md @@ -0,0 +1,15 @@ +--- +title: React Server-side tree data +--- + +# Data Grid - Server-side tree data 🚧 + +

Tree data lazy-loading with server side data source.

+ +:::warning +This feature isn't implemented yet. It's coming. + +👍 Upvote [issue #3377](https://github.com/mui/mui-x/issues/3377) if you want to see it land faster. + +Don't hesitate to leave a comment on the same issue to influence what gets built. Especially if you already have a use case for this component, or if you are facing a pain point with the [currently proposed workaround](https://mui.com/x/react-data-grid/tree-data/#children-lazy-loading). +::: diff --git a/docs/data/pages.ts b/docs/data/pages.ts index eec89d14ac49..1ec18593c19e 100644 --- a/docs/data/pages.ts +++ b/docs/data/pages.ts @@ -93,11 +93,6 @@ const pages: MuiPage[] = [ { pathname: '/x/react-data-grid/virtualization' }, { pathname: '/x/react-data-grid/accessibility' }, { pathname: '/x/react-data-grid/performance' }, - { - pathname: '/x/react-data-grid/server-side-data', - title: 'Server-side data 🚧', - plan: 'pro', - }, { pathname: '/x/react-data-grid-group-pivot', title: 'Group & Pivot', @@ -109,6 +104,39 @@ const pages: MuiPage[] = [ { pathname: '/x/react-data-grid/pivoting', title: 'Pivoting 🚧', plan: 'premium' }, ], }, + { + pathname: '/x/react-data-grid/server-side-data-group', + title: 'Server-side data 🚧', + plan: 'pro', + children: [ + { pathname: '/x/react-data-grid/server-side-data', title: 'Overview', plan: 'pro' }, + { + pathname: '/x/react-data-grid/server-side-data/lazy-loading', + title: 'Lazy loading 🚧', + plan: 'pro', + }, + { + pathname: '/x/react-data-grid/server-side-data/infinite-loading', + title: 'Infinite loading 🚧', + plan: 'pro', + }, + { + pathname: '/x/react-data-grid/server-side-data/tree-data', + title: 'Tree data 🚧', + plan: 'pro', + }, + { + pathname: '/x/react-data-grid/server-side-data/row-grouping', + title: 'Row grouping 🚧', + plan: 'pro', + }, + { + pathname: '/x/react-data-grid/server-side-data/aggregation', + title: 'Aggregation 🚧', + plan: 'premium', + }, + ], + }, { title: 'Advanced', pathname: '/x/react-data-grid/advanced', diff --git a/docs/pages/x/react-data-grid/server-side-data/aggregation.js b/docs/pages/x/react-data-grid/server-side-data/aggregation.js new file mode 100644 index 000000000000..35594f6fa641 --- /dev/null +++ b/docs/pages/x/react-data-grid/server-side-data/aggregation.js @@ -0,0 +1,7 @@ +import * as React from 'react'; +import MarkdownDocs from 'docs/src/modules/components/MarkdownDocs'; +import * as pageProps from 'docsx/data/data-grid/server-side-data/aggregation.md?@mui/markdown'; + +export default function Page() { + return ; +} diff --git a/docs/pages/x/react-data-grid/server-side-data/infinite-loading.js b/docs/pages/x/react-data-grid/server-side-data/infinite-loading.js new file mode 100644 index 000000000000..9b9c08c05e85 --- /dev/null +++ b/docs/pages/x/react-data-grid/server-side-data/infinite-loading.js @@ -0,0 +1,7 @@ +import * as React from 'react'; +import MarkdownDocs from 'docs/src/modules/components/MarkdownDocs'; +import * as pageProps from 'docsx/data/data-grid/server-side-data/infinite-loading.md?@mui/markdown'; + +export default function Page() { + return ; +} diff --git a/docs/pages/x/react-data-grid/server-side-data/lazy-loading.js b/docs/pages/x/react-data-grid/server-side-data/lazy-loading.js new file mode 100644 index 000000000000..f0b78beb6cac --- /dev/null +++ b/docs/pages/x/react-data-grid/server-side-data/lazy-loading.js @@ -0,0 +1,7 @@ +import * as React from 'react'; +import MarkdownDocs from 'docs/src/modules/components/MarkdownDocs'; +import * as pageProps from 'docsx/data/data-grid/server-side-data/lazy-loading.md?@mui/markdown'; + +export default function Page() { + return ; +} diff --git a/docs/pages/x/react-data-grid/server-side-data/row-grouping.js b/docs/pages/x/react-data-grid/server-side-data/row-grouping.js new file mode 100644 index 000000000000..06017357e71e --- /dev/null +++ b/docs/pages/x/react-data-grid/server-side-data/row-grouping.js @@ -0,0 +1,7 @@ +import * as React from 'react'; +import MarkdownDocs from 'docs/src/modules/components/MarkdownDocs'; +import * as pageProps from 'docsx/data/data-grid/server-side-data/row-grouping.md?@mui/markdown'; + +export default function Page() { + return ; +} diff --git a/docs/pages/x/react-data-grid/server-side-data/tree-data.js b/docs/pages/x/react-data-grid/server-side-data/tree-data.js new file mode 100644 index 000000000000..6d122e9e139f --- /dev/null +++ b/docs/pages/x/react-data-grid/server-side-data/tree-data.js @@ -0,0 +1,7 @@ +import * as React from 'react'; +import MarkdownDocs from 'docs/src/modules/components/MarkdownDocs'; +import * as pageProps from 'docsx/data/data-grid/server-side-data/tree-data.md?@mui/markdown'; + +export default function Page() { + return ; +} diff --git a/packages/grid/x-data-grid-pro/src/models/dataSource.ts b/packages/grid/x-data-grid-pro/src/models/dataSource.ts index 01b5ff748ea0..e6dc9236529e 100644 --- a/packages/grid/x-data-grid-pro/src/models/dataSource.ts +++ b/packages/grid/x-data-grid-pro/src/models/dataSource.ts @@ -9,19 +9,52 @@ import { interface GetRowsParams { sortModel: GridSortModel; filterModel: GridFilterModel; - groupKeys: string[]; // array of keys returned by `getGroupKey` of all the parent rows until the row for which the data is requested - paginationModel: GridPaginationModel; // alternate to `start`, `end` + /** + * Alternate to `start` and `end`, maps to `GridPaginationModel` interface + */ + paginationModel: GridPaginationModel; + /** + * First row index to fetch (number) or cursor information (number | string) + */ start: number | string; // first row index to fetch or cursor information + /** + * Last row index to fetch + */ end: number; // last row index to fetch + /** + * Array of keys returned by `getGroupKey` of all the parent rows until the row for which the data is requested + * `getGroupKey` prop must be implemented to use this + * Useful for `treeData` and `rowGrouping` only + */ + groupKeys: string[]; + /** + * List of grouped columns (only applicable with `rowGrouping`) + */ groupFields: GridColDef['field'][]; // list of grouped columns (`rowGrouping`) } interface GetRowsResponse { rows: GridRowModel[]; - rowCount?: number; // optional: to reflect updates in total `rowCount` + /** + * To reflect updates in total `rowCount` (optional) + * Useful when the `rowCount` is inaccurate (e.g. when filtering) or not available upfront + */ + rowCount?: number; + /** + * Additional `pageInfo` to help the grid determine if there are more rows to fetch (corner-cases) + * `hasNextPage`: When row count is unknown/inaccurate, if `truncated` is set or rowCount is not known, data will keep loading until `hasNextPage` is `false` + * `truncated`: To reflect `rowCount` is inaccurate (will trigger `x-y of many` in pagination after the count of rows fetched is greater than provided `rowCount`) + * It could be useful with: + * 1. Cursor based pagination: + * When rowCount is not known, grid will check for `hasNextPage` to determine + * if there are more rows to fetch. + * 2. Inaccurate `rowCount`: + * `truncated: true` will let the grid know that `rowCount` is estimated/truncated. + * Thus `hasNextPage` will come into play to check more rows are available to fetch after the number becomes >= provided `rowCount` + */ pageInfo?: { - hasNextPage?: boolean; // optional: when row count is unknown/inaccurate, if `truncated` is set or rowCount is not known, data will keep loading until `hasNextPage` is `false` - truncated?: number; // optional: to reflect rowCount is inaccurate (will trigger `x-y of many` in pagination) + hasNextPage?: boolean; + truncated?: number; }; } @@ -32,29 +65,8 @@ export interface DataSource { - `updateRow` is optional `getRows` will be used by the grid to fetch data for the current page or children for the current parent group - It may return a `rowCount` to update the total count of rows in the grid + It may return a `rowCount` to update the total count of rows in the grid along with the optional `pageInfo` */ getRows(params: GetRowsParams): Promise; - // if passed, will be called like `processRowUpdate` on row edit updateRow?(rows: GridRowModel): Promise; - - /** - Functions related to grouped data (`treeData` and `rowGrouping`): - - `getGroupKey` is required - - `hasChildren` is required - - `getChildrenCount` is optional - - `getGroupKey` will be used by the grid to group rows by their parent group - This effectively replaces `getTreeDataPath`. - Consider this structure: - - (1) Sarah // groupKey 'Sarah' - - (2) Thomas // groupKey 'Thomas' - When (2) is expanded, the `load` function will be called with group keys ['Sarah', 'Thomas']. - `hasChildren` will be used by the grid to determine if a row has children on server - `getChildrenCount` will be used by the grid to determine the number of children of a row on server - */ - getGroupKey(row: GridRowModel): string; // returns the key of the group to which the row belongs (`treeData` and `rowGrouping`) - hasChildren(row: GridRowModel): boolean; // return `true` if row has children on server - getChildrenCount?: (row: GridRowModel) => number; // optional child count - // these /\ 3 could also be root props of the grid } From 03845e5910b486911e196f1781b127896bd24530 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Wed, 1 Nov 2023 16:03:48 +0500 Subject: [PATCH 5/7] Fix markdownlint --- docs/data/data-grid/server-side-data/index.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/data/data-grid/server-side-data/index.md b/docs/data/data-grid/server-side-data/index.md index b9e547934300..0b8cd672f681 100644 --- a/docs/data/data-grid/server-side-data/index.md +++ b/docs/data/data-grid/server-side-data/index.md @@ -226,11 +226,10 @@ Props related to grouped data (`treeData` and `rowGrouping`): This effectively replaces `getTreeDataPath`. Consider this structure: - ``` + ```js - (1) Sarah // groupKey 'Sarah' - (2) Thomas // groupKey 'Thomas' ``` - When (2) is expanded, the `getRows` function will be called with group keys `['Sarah', 'Thomas']`. - `hasChildren?(row: GridRowModel): boolean` From 7af321727ed5a86f745e016f319522d90e22a148 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Wed, 1 Nov 2023 16:06:12 +0500 Subject: [PATCH 6/7] Improvement --- docs/data/data-grid/server-side-data/index.md | 1 + docs/data/pages.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/data/data-grid/server-side-data/index.md b/docs/data/data-grid/server-side-data/index.md index 0b8cd672f681..230ca1f5e8cf 100644 --- a/docs/data/data-grid/server-side-data/index.md +++ b/docs/data/data-grid/server-side-data/index.md @@ -230,6 +230,7 @@ Props related to grouped data (`treeData` and `rowGrouping`): - (1) Sarah // groupKey 'Sarah' - (2) Thomas // groupKey 'Thomas' ``` + When (2) is expanded, the `getRows` function will be called with group keys `['Sarah', 'Thomas']`. - `hasChildren?(row: GridRowModel): boolean` diff --git a/docs/data/pages.ts b/docs/data/pages.ts index 4b712dd99919..4302da61b631 100644 --- a/docs/data/pages.ts +++ b/docs/data/pages.ts @@ -110,7 +110,7 @@ const pages: MuiPage[] = [ title: 'Server-side data 🚧', plan: 'pro', children: [ - { pathname: '/x/react-data-grid/server-side-data', title: 'Overview', plan: 'pro' }, + { pathname: '/x/react-data-grid/server-side-data', title: 'Overview' }, { pathname: '/x/react-data-grid/server-side-data/lazy-loading', title: 'Lazy loading 🚧', From 7d3413bc2bb4eecb6470455faf9967c07cd70de6 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Tue, 7 Nov 2023 14:41:59 +0500 Subject: [PATCH 7/7] Apply suggestions from code review Signed-off-by: Bilal Shafi --- docs/data/data-grid/server-side-data/index.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/data/data-grid/server-side-data/index.md b/docs/data/data-grid/server-side-data/index.md index 230ca1f5e8cf..f235a0558183 100644 --- a/docs/data/data-grid/server-side-data/index.md +++ b/docs/data/data-grid/server-side-data/index.md @@ -10,7 +10,7 @@ title: React Data Grid - Server-side data Managing server-side data efficiently in a React application can become complex as the dataset grows. -Without a dedicated module that abstracts it's complexities, developers often face challenges related to manual data fetching, pagination, sorting, filtering, and it's often gets trickier to tackle performance issues, which can lead to a poor user experience. +Without a dedicated module that abstracts its complexities, developers often face challenges related to manual data fetching, pagination, sorting, and filtering, and it often gets trickier to tackle performance issues, which can lead to a poor user experience. Have a look at an example: @@ -60,7 +60,7 @@ React.useEffect(() => { />; ``` -We only scratched the surface in the above example with a lot of problems still unsolved like: +This example only scratches the surface with a lot of problems still unsolved like: - Performance optimization - Caching data/deduping requests @@ -74,7 +74,7 @@ Trying to solve these problems one after the other can make the code complex and ## Data source -A very common pattern to solve these problems is to use a centralized data source. A data source is an abstraction layer that sits between the data grid and the server. It provides a simple interface to the data grid to fetch data and update it. It handles a lot of the complexities related to server side data fetching. We will talk more about the data source in the next section. +A very common pattern to solve these problems is to use a centralized data source. A data source is an abstraction layer that sits between the data grid and the server. It provides a simple interface to the data grid to fetch data and update it. It handles a lot of the complexities related to server-side data fetching. Let's delve a bit deeper into how it will look like. :::warning @@ -84,7 +84,7 @@ This feature is still under development and the information shared on thi ### Overview -The Data Grid already supports manual server-side data fetching for features like sorting, filtering, etc. In order to make it more powerful and simple to use, we provide a data source interface that you can implement with your existing data fetching logic. +The Data Grid already supports manual server-side data fetching for features like sorting, filtering, etc. In order to make it more powerful and simple to use, the grid will support a data source interface that you can implement with your existing data fetching logic. The datasource will work with all the major data grid features which require server-side data fetching such as sorting, filtering, pagination, grouping, etc. @@ -107,7 +107,7 @@ interface DataSource { } ``` -Here's how the code will look like for the above example: +Here's how the code will look like for the above example when implemented with data source: ```tsx const customDataSource: DataSource = { @@ -210,13 +210,13 @@ interface GetRowsResponse { If provided, the method `dataSource.updateRow` will be called with the `GridRowModel` object whenever the user edits a row. This method is optional and you can skip it if you don't need to update the data on the server. It will work in a similar way as the `processRowUpdate` prop. -#### Data grid props +#### Data Grid props These data grid props will work with the server-side data source: - `dataSource: DataSource`: the data source object that you need to implement - `rows`: will be ignored, could be skipped when `dataSource` is provided -- `rowCount`: will be used to identify the total number of rows in the grid, if not provided, the grid will use the _GetRowsResponse.rowCount_ value +- `rowCount`: will be used to identify the total number of rows in the grid, if not provided, the grid will check for the _GetRowsResponse.rowCount_ value, unless the feature being used is infinite loading where no `rowCount` is available at all. Props related to grouped data (`treeData` and `rowGrouping`):