Lazy Sub-Rows Example
If you have a ton of nested data that you want to display, but you don't want to fetch it all up front, you can set up Material React Table to only fetch the sub-rows data when the user expands the row.
There are quite a few ways in which you could implement fetching sub-rows lazily. This example is just one way to do it.
This example combines concepts from the React Query Example and the Expanding Parsed Tree Example.
First Name | Last Name | Email | State | |
---|---|---|---|---|
10
1import { useMemo, useState } from 'react';2import {3 MaterialReactTable,4 useMaterialReactTable,5 type MRT_ColumnDef,6 type MRT_PaginationState,7 type MRT_SortingState,8 type MRT_ExpandedState,9} from 'material-react-table';10import {11 QueryClient,12 QueryClientProvider,13 keepPreviousData,14 useQuery,15} from '@tanstack/react-query'; //note: this is TanStack React Query V51617//Your API response shape will probably be different. Knowing a total row count is important though.18type UserApiResponse = {19 data: Array<User>;20 meta: {21 totalRowCount: number;22 };23};2425type User = {26 id: string;27 firstName: string;28 lastName: string;29 email: string;30 state: string;31 managerId: string | null; //row's parent row id32 subordinateIds: string[]; //or some type of boolean that indicates that there are sub-rows33};3435const columns: MRT_ColumnDef<User>[] = [36 //column definitions...54];5556const Example = () => {57 const [sorting, setSorting] = useState<MRT_SortingState>([]);58 const [pagination, setPagination] = useState<MRT_PaginationState>({59 pageIndex: 0,60 pageSize: 10,61 });62 const [expanded, setExpanded] = useState<MRT_ExpandedState>({}); //Record<string, boolean> | true6364 //which rows have sub-rows expanded and need their direct sub-rows to be included in the API call65 const expandedRowIds: string[] | 'all' = useMemo(66 () =>67 expanded === true68 ? 'all'69 : Object.entries(expanded)70 .filter(([_managerId, isExpanded]) => isExpanded)71 .map(([managerId]) => managerId),72 [expanded],73 );7475 const {76 data: { data = [], meta } = {},77 isError,78 isRefetching,79 isLoading,80 } = useFetchUsers({81 pagination,82 sorting,83 expandedRowIds,84 });8586 //get data for root rows only (top of the tree data)87 const rootData = useMemo(() => data.filter((r) => !r.managerId), [data]);8889 const table = useMaterialReactTable({90 columns,91 data: rootData,92 enableExpanding: true, //enable expanding column93 enableFilters: false,94 //tell MRT which rows have additional sub-rows that can be fetched95 getRowCanExpand: (row) => !!row.original.subordinateIds.length, //just some type of boolean96 //identify rows by the user's id97 getRowId: (row) => row.id,98 //if data is delivered in a flat array, MRT can convert it to a tree structure99 //though it's usually better if the API can construct the nested structure before this point100 getSubRows: (row) => data.filter((r) => r.managerId === row.id), //parse flat array into tree structure101 // paginateExpandedRows: false, //the back-end in this example is acting as if this option is false102 manualPagination: true, //turn off built-in client-side pagination103 manualSorting: true, //turn off built-in client-side sorting104 muiToolbarAlertBannerProps: isError105 ? {106 color: 'error',107 children: 'Error loading data',108 }109 : undefined,110 onExpandedChange: setExpanded,111 onPaginationChange: setPagination,112 onSortingChange: setSorting,113 rowCount: meta?.totalRowCount ?? 0,114 state: {115 expanded,116 isLoading,117 pagination,118 showAlertBanner: isError,119 showProgressBars: isRefetching,120 sorting,121 },122 });123124 return <MaterialReactTable table={table} />;125};126127const queryClient = new QueryClient();128129const ExampleWithReactQueryProvider = () => (130 //App.tsx or AppProviders file. Don't just wrap this component with QueryClientProvider! Wrap your whole App!131 <QueryClientProvider client={queryClient}>132 <Example />133 </QueryClientProvider>134);135136export default ExampleWithReactQueryProvider;137138//fetch user hook139const useFetchUsers = ({140 pagination,141 sorting,142 expandedRowIds,143}: {144 pagination: MRT_PaginationState;145 sorting: MRT_SortingState;146 expandedRowIds: string[] | 'all';147}) => {148 return useQuery<UserApiResponse>({149 queryKey: [150 'users', //give a unique key for this query151 pagination.pageIndex, //refetch when pagination.pageIndex changes152 pagination.pageSize, //refetch when pagination.pageSize changes153 sorting, //refetch when sorting changes154 expandedRowIds,155 ],156 queryFn: async () => {157 const fetchURL = new URL('/api/treedata', location.origin);158159 //read our state and pass it to the API as query params160 fetchURL.searchParams.set(161 'start',162 `${pagination.pageIndex * pagination.pageSize}`,163 );164 fetchURL.searchParams.set('size', `${pagination.pageSize}`);165 fetchURL.searchParams.set('sorting', JSON.stringify(sorting ?? []));166 fetchURL.searchParams.set(167 'expandedRowIds',168 expandedRowIds === 'all' ? 'all' : JSON.stringify(expandedRowIds ?? []),169 );170171 //use whatever fetch library you want, fetch, axios, etc172 const response = await fetch(fetchURL.href);173 const json = (await response.json()) as UserApiResponse;174 return json;175 },176 placeholderData: keepPreviousData, //don't go to 0 rows when refetching or paginating to next page177 });178};179
View Extra Storybook Examples