Lazy Detail Panel Example
Fetching the additional data for the detail panels only after the user clicks to expand the row can be a good way to improve performance, and it is pretty easy to implement in Material React Table.
It's even easier if you are using React Query. In this example, react query is used to fetch the data for the detail panel, but only after the user clicks to expand the row. The data is also cached for a certain period of time so that when the detail panel is closed and reopened, the data is already available and won't need to be re-fetched.
First Name | Last Name | Address | State | Phone Number | |
---|---|---|---|---|---|
5
1import { useMemo, useState } from 'react';2import {3 MaterialReactTable,4 useMaterialReactTable,5 type MRT_ColumnDef,6 type MRT_ColumnFiltersState,7 type MRT_PaginationState,8 type MRT_SortingState,9 type MRT_Row,10} from 'material-react-table';11import { Alert, CircularProgress, Stack } from '@mui/material';12import AddIcon from '@mui/icons-material/Add';13import MinusIcon from '@mui/icons-material/Remove';14import {15 QueryClient,16 QueryClientProvider,17 keepPreviousData,18 useQuery,19} from '@tanstack/react-query'; //note: this is TanStack React Query V52021//Your API response shape will probably be different. Knowing a total row count is important though.22type UserApiResponse = {23 data: Array<User>;24 meta: {25 totalRowCount: number;26 };27};2829type User = {30 firstName: string;31 lastName: string;32 address: string;33 state: string;34 phoneNumber: string;35 lastLogin: Date;36};3738type FullUserInfoApiResponse = FullUserInfo;3940type FullUserInfo = User & {41 favoriteMusic: string;42 favoriteSong: string;43 quote: string;44};4546const DetailPanel = ({ row }: { row: MRT_Row<User> }) => {47 const {48 data: userInfo,49 isLoading,50 isError,51 } = useFetchUserInfo(52 {53 phoneNumber: row.id, //the row id is set to the user's phone number54 },55 {56 enabled: row.getIsExpanded(),57 },58 );59 if (isLoading) return <CircularProgress />;60 if (isError) return <Alert severity="error">Error Loading User Info</Alert>;6162 const { favoriteMusic, favoriteSong, quote } = userInfo ?? {};6364 return (65 <Stack gap="0.5rem" minHeight="00px">66 <div>67 <b>Favorite Music:</b> {favoriteMusic}68 </div>69 <div>70 <b>Favorite Song:</b> {favoriteSong}71 </div>72 <div>73 <b>Quote:</b> {quote}74 </div>75 </Stack>76 );77};7879const Example = () => {80 //manage our own state for stuff we want to pass to the API81 const [columnFilters, setColumnFilters] = useState<MRT_ColumnFiltersState>(82 [],83 );84 const [globalFilter, setGlobalFilter] = useState('');85 const [sorting, setSorting] = useState<MRT_SortingState>([]);86 const [pagination, setPagination] = useState<MRT_PaginationState>({87 pageIndex: 0,88 pageSize: 5,89 });9091 const {92 data: { data = [], meta } = {},93 isError,94 isRefetching,95 isLoading,96 } = useFetchUsers({97 columnFilters,98 globalFilter,99 pagination,100 sorting,101 });102103 const columns = useMemo<MRT_ColumnDef<User>[]>(104 //column definitions...129 );130131 const table = useMaterialReactTable({132 columns,133 data,134 getRowId: (row) => row.phoneNumber,135 manualFiltering: true, //turn off built-in client-side filtering136 manualPagination: true, //turn off built-in client-side pagination137 manualSorting: true, //turn off built-in client-side sorting138 muiExpandButtonProps: ({ row }) => ({139 children: row.getIsExpanded() ? <MinusIcon /> : <AddIcon />,140 }),141 muiToolbarAlertBannerProps: isError142 ? {143 color: 'error',144 children: 'Error loading data',145 }146 : undefined,147 onColumnFiltersChange: setColumnFilters,148 onGlobalFilterChange: setGlobalFilter,149 onPaginationChange: setPagination,150 onSortingChange: setSorting,151 renderDetailPanel: ({ row }) => <DetailPanel row={row} />,152 rowCount: meta?.totalRowCount ?? 0,153 state: {154 columnFilters,155 globalFilter,156 isLoading,157 pagination,158 showAlertBanner: isError,159 showProgressBars: isRefetching,160 sorting,161 },162 });163164 return <MaterialReactTable table={table} />;165};166167const queryClient = new QueryClient();168169const ExampleWithReactQueryProvider = () => (170 //App.tsx or AppProviders file. Don't just wrap this component with QueryClientProvider! Wrap your whole App!171 <QueryClientProvider client={queryClient}>172 <Example />173 </QueryClientProvider>174);175176export default ExampleWithReactQueryProvider;177178//fetch user hook179const useFetchUsers = ({180 columnFilters,181 globalFilter,182 pagination,183 sorting,184}: {185 columnFilters: MRT_ColumnFiltersState;186 globalFilter: string;187 pagination: MRT_PaginationState;188 sorting: MRT_SortingState;189}) => {190 return useQuery<UserApiResponse>({191 queryKey: [192 'users', //give a unique key for this query193 columnFilters, //refetch when columnFilters changes194 globalFilter, //refetch when globalFilter changes195 pagination.pageIndex, //refetch when pagination.pageIndex changes196 pagination.pageSize, //refetch when pagination.pageSize changes197 sorting, //refetch when sorting changes198 ],199 queryFn: async () => {200 const fetchURL = new URL(201 '/api/data',202 process.env.NODE_ENV === 'production'203 ? 'https://www.material-react-table.com'204 : 'http://localhost:3000',205 );206207 //read our state and pass it to the API as query params208 fetchURL.searchParams.set(209 'start',210 `${pagination.pageIndex * pagination.pageSize}`,211 );212 fetchURL.searchParams.set('size', `${pagination.pageSize}`);213 fetchURL.searchParams.set('filters', JSON.stringify(columnFilters ?? []));214 fetchURL.searchParams.set('globalFilter', globalFilter ?? '');215 fetchURL.searchParams.set('sorting', JSON.stringify(sorting ?? []));216217 //use whatever fetch library you want, fetch, axios, etc218 const response = await fetch(fetchURL.href);219 const json = (await response.json()) as UserApiResponse;220 return json;221 },222 placeholderData: keepPreviousData, //don't go to 0 rows when refetching or paginating to next page223 });224};225226//fetch more user info hook227const useFetchUserInfo = (228 params: { phoneNumber: string },229 options: { enabled: boolean },230) => {231 return useQuery<FullUserInfoApiResponse>({232 enabled: options.enabled, //only fetch when the detail panel is opened233 staleTime: 60 * 1000, //don't refetch for 60 seconds234 queryKey: ['user', params.phoneNumber], //give a unique key for this query for each user fetch235 queryFn: async () => {236 const fetchURL = new URL(237 `/api/moredata/${params.phoneNumber238 .replaceAll('-', '')239 .replaceAll('.', '')240 .replaceAll('(', '')241 .replaceAll(')', '')}`,242 process.env.NODE_ENV === 'production'243 ? 'https://www.material-react-table.com'244 : 'http://localhost:3000',245 );246247 //use whatever fetch library you want, fetch, axios, etc248 const response = await fetch(fetchURL.href);249 const json = (await response.json()) as FullUserInfoApiResponse;250 return json;251 },252 });253};254
View Extra Storybook Examples