React Query (Remote) Example
This is just like the Remote Data Example, but react-query is used to simplify all the state management of the fetching and loading of data.
React Query a.k.a TanStack Query is a library made by the same team that made TanStack Table and is the best way to fetch data in client-side React applications. It has a ton of features that make working with table features a lot easier. It is highly recommended that you investigate using it in your own applications.
Also, be sure to check out the Virtualized Example, which shows off the use of another TanStack library, TanStack React Virtual, to render thousands of rows at once while still maintaining great performance.
First Name | Last Name | Address | State | Phone Number |
---|---|---|---|---|
0-0 of 0
1import { useMemo, useState } from 'react';2import {3MantineReactTable,4useMantineReactTable,5type MRT_ColumnDef,6type MRT_ColumnFiltersState,7type MRT_PaginationState,8type MRT_SortingState,9type MRT_ColumnFilterFnsState,10} from 'mantine-react-table';11import { ActionIcon, Tooltip } from '@mantine/core';12import { IconRefresh } from '@tabler/icons-react';13import {14QueryClient,15QueryClientProvider,16useQuery,17} from '@tanstack/react-query';1819type User = {20firstName: string;21lastName: string;22address: string;23state: string;24phoneNumber: string;25};2627type UserApiResponse = {28data: Array<User>;29meta: {30totalRowCount: number;31};32};3334interface Params {35columnFilterFns: MRT_ColumnFilterFnsState;36columnFilters: MRT_ColumnFiltersState;37globalFilter: string;38sorting: MRT_SortingState;39pagination: MRT_PaginationState;40}4142//custom react-query hook43const useGetUsers = ({44columnFilterFns,45columnFilters,46globalFilter,47sorting,48pagination,49}: Params) => {50//build the URL (https://www.mantine-react-table.com/api/data?start=0&size=10&filters=[]&globalFilter=&sorting=[])51const fetchURL = new URL(52'/api/data',53process.env.NODE_ENV === 'production'54? 'https://www.mantine-react-table.com'55: 'http://localhost:3001',56);57fetchURL.searchParams.set(58'start',59`${pagination.pageIndex * pagination.pageSize}`,60);61fetchURL.searchParams.set('size', `${pagination.pageSize}`);62fetchURL.searchParams.set('filters', JSON.stringify(columnFilters ?? []));63fetchURL.searchParams.set(64'filterModes',65JSON.stringify(columnFilterFns ?? {}),66);67fetchURL.searchParams.set('globalFilter', globalFilter ?? '');68fetchURL.searchParams.set('sorting', JSON.stringify(sorting ?? []));6970return useQuery<UserApiResponse>({71queryKey: ['users', fetchURL.href], //refetch whenever the URL changes (columnFilters, globalFilter, sorting, pagination)72queryFn: () => fetch(fetchURL.href).then((res) => res.json()),73keepPreviousData: true, //useful for paginated queries by keeping data from previous pages on screen while fetching the next page74staleTime: 30_000, //don't refetch previously viewed pages until cache is more than 30 seconds old75});76};7778const Example = () => {79const columns = useMemo<MRT_ColumnDef<User>[]>(80() => [81{82accessorKey: 'firstName',83header: 'First Name',84},85{86accessorKey: 'lastName',87header: 'Last Name',88},89{90accessorKey: 'address',91header: 'Address',92},93{94accessorKey: 'state',95header: 'State',96},97{98accessorKey: 'phoneNumber',99header: 'Phone Number',100},101],102[],103);104105//Manage MRT state that we want to pass to our API106const [columnFilters, setColumnFilters] = useState<MRT_ColumnFiltersState>(107[],108);109const [columnFilterFns, setColumnFilterFns] = //filter modes110useState<MRT_ColumnFilterFnsState>(111Object.fromEntries(112columns.map(({ accessorKey }) => [accessorKey, 'contains']),113),114); //default to "contains" for all columns115const [globalFilter, setGlobalFilter] = useState('');116const [sorting, setSorting] = useState<MRT_SortingState>([]);117const [pagination, setPagination] = useState<MRT_PaginationState>({118pageIndex: 0,119pageSize: 10,120});121122//call our custom react-query hook123const { data, isError, isFetching, isLoading, refetch } = useGetUsers({124columnFilterFns,125columnFilters,126globalFilter,127pagination,128sorting,129});130131//this will depend on your API response shape132const fetchedUsers = data?.data ?? [];133const totalRowCount = data?.meta?.totalRowCount ?? 0;134135const table = useMantineReactTable({136columns,137data: fetchedUsers,138enableColumnFilterModes: true,139columnFilterModeOptions: ['contains', 'startsWith', 'endsWith'],140initialState: { showColumnFilters: true },141manualFiltering: true,142manualPagination: true,143manualSorting: true,144mantineToolbarAlertBannerProps: isError145? {146color: 'red',147children: 'Error loading data',148}149: undefined,150onColumnFilterFnsChange: setColumnFilterFns,151onColumnFiltersChange: setColumnFilters,152onGlobalFilterChange: setGlobalFilter,153onPaginationChange: setPagination,154onSortingChange: setSorting,155renderTopToolbarCustomActions: () => (156<Tooltip label="Refresh Data">157<ActionIcon onClick={() => refetch()}>158<IconRefresh />159</ActionIcon>160</Tooltip>161),162rowCount: totalRowCount,163state: {164columnFilterFns,165columnFilters,166globalFilter,167isLoading,168pagination,169showAlertBanner: isError,170showProgressBars: isFetching,171sorting,172},173});174175return <MantineReactTable table={table} />;176};177178const queryClient = new QueryClient();179180const ExampleWithReactQueryProvider = () => (181//Put this with your other react-query providers near root of your app182<QueryClientProvider client={queryClient}>183<Example />184</QueryClientProvider>185);186187export default ExampleWithReactQueryProvider;
1import { useMemo, useState } from 'react';2import { MantineReactTable, useMantineReactTable } from 'mantine-react-table';3import { ActionIcon, Tooltip } from '@mantine/core';4import { IconRefresh } from '@tabler/icons-react';5import {6QueryClient,7QueryClientProvider,8useQuery,9} from '@tanstack/react-query';1011//custom react-query hook12const useGetUsers = ({13columnFilterFns,14columnFilters,15globalFilter,16sorting,17pagination,18}) => {19//build the URL (https://www.mantine-react-table.com/api/data?start=0&size=10&filters=[]&globalFilter=&sorting=[])20const fetchURL = new URL(21'/api/data',22process.env.NODE_ENV === 'production'23? 'https://www.mantine-react-table.com'24: 'http://localhost:3001',25);26fetchURL.searchParams.set(27'start',28`${pagination.pageIndex * pagination.pageSize}`,29);30fetchURL.searchParams.set('size', `${pagination.pageSize}`);31fetchURL.searchParams.set('filters', JSON.stringify(columnFilters ?? []));32fetchURL.searchParams.set(33'filterModes',34JSON.stringify(columnFilterFns ?? {}),35);36fetchURL.searchParams.set('globalFilter', globalFilter ?? '');37fetchURL.searchParams.set('sorting', JSON.stringify(sorting ?? []));3839return useQuery({40queryKey: ['users', fetchURL.href], //refetch whenever the URL changes (columnFilters, globalFilter, sorting, pagination)41queryFn: () => fetch(fetchURL.href).then((res) => res.json()),42keepPreviousData: true, //useful for paginated queries by keeping data from previous pages on screen while fetching the next page43staleTime: 30_000, //don't refetch previously viewed pages until cache is more than 30 seconds old44});45};4647const Example = () => {48const columns = useMemo(49() => [50{51accessorKey: 'firstName',52header: 'First Name',53},54{55accessorKey: 'lastName',56header: 'Last Name',57},58{59accessorKey: 'address',60header: 'Address',61},62{63accessorKey: 'state',64header: 'State',65},66{67accessorKey: 'phoneNumber',68header: 'Phone Number',69},70],71[],72);7374//Manage MRT state that we want to pass to our API75const [columnFilters, setColumnFilters] = useState([]);76const [columnFilterFns, setColumnFilterFns] = //filter modes77useState(78Object.fromEntries(79columns.map(({ accessorKey }) => [accessorKey, 'contains']),80),81); //default to "contains" for all columns82const [globalFilter, setGlobalFilter] = useState('');83const [sorting, setSorting] = useState([]);84const [pagination, setPagination] = useState({85pageIndex: 0,86pageSize: 10,87});8889//call our custom react-query hook90const { data, isError, isFetching, isLoading, refetch } = useGetUsers({91columnFilterFns,92columnFilters,93globalFilter,94pagination,95sorting,96});9798//this will depend on your API response shape99const fetchedUsers = data?.data ?? [];100const totalRowCount = data?.meta?.totalRowCount ?? 0;101102const table = useMantineReactTable({103columns,104data: fetchedUsers,105enableColumnFilterModes: true,106columnFilterModeOptions: ['contains', 'startsWith', 'endsWith'],107initialState: { showColumnFilters: true },108manualFiltering: true,109manualPagination: true,110manualSorting: true,111mantineToolbarAlertBannerProps: isError112? {113color: 'red',114children: 'Error loading data',115}116: undefined,117onColumnFilterFnsChange: setColumnFilterFns,118onColumnFiltersChange: setColumnFilters,119onGlobalFilterChange: setGlobalFilter,120onPaginationChange: setPagination,121onSortingChange: setSorting,122renderTopToolbarCustomActions: () => (123<Tooltip label="Refresh Data">124<ActionIcon onClick={() => refetch()}>125<IconRefresh />126</ActionIcon>127</Tooltip>128),129rowCount: totalRowCount,130state: {131columnFilterFns,132columnFilters,133globalFilter,134isLoading,135pagination,136showAlertBanner: isError,137showProgressBars: isFetching,138sorting,139},140});141142return <MantineReactTable table={table} />;143};144145const queryClient = new QueryClient();146147const ExampleWithReactQueryProvider = () => (148//Put this with your other react-query providers near root of your app149<QueryClientProvider client={queryClient}>150<Example />151</QueryClientProvider>152);153154export default ExampleWithReactQueryProvider;
1import {2type MRT_ColumnFiltersState,3type MRT_SortingState,4} from 'mantine-react-table';5import { type NextApiRequest, type NextApiResponse } from 'next';6import { getData } from './mock';78//This is just a simple mock of a backend API where you would do server-side pagination, filtering, and sorting9//You would most likely want way more validation and error handling than this in a real world application10//Also most of this logic should actually be in the database query itself, but this is just a mock11export default function handler(req: NextApiRequest, res: NextApiResponse) {12let dbData = getData();13const { start, size, filters, filterModes, sorting, globalFilter } =14req.query as Record<string, string>;1516const parsedFilterModes = JSON.parse(filterModes ?? '{}') as Record<17string,18string19>;2021const parsedColumnFilters = JSON.parse(filters) as MRT_ColumnFiltersState;22if (parsedColumnFilters?.length) {23parsedColumnFilters.map((filter) => {24const { id: columnId, value: filterValue } = filter;25const filterMode = parsedFilterModes?.[columnId] ?? 'contains';26dbData = dbData.filter((row) => {27const rowValue = row[columnId]?.toString()?.toLowerCase();28if (filterMode === 'contains') {29return rowValue.includes?.((filterValue as string).toLowerCase());30} else if (filterMode === 'startsWith') {31return rowValue.startsWith?.((filterValue as string).toLowerCase());32} else if (filterMode === 'endsWith') {33return rowValue.endsWith?.((filterValue as string).toLowerCase());34}35});36});37}3839if (globalFilter) {40dbData = dbData.filter((row) =>41Object.keys(row).some(42(columnId) =>43row[columnId]44?.toString()45?.toLowerCase()46?.includes?.((globalFilter as string).toLowerCase()),47),48);49}5051const parsedSorting = JSON.parse(sorting) as MRT_SortingState;52if (parsedSorting?.length) {53const sort = parsedSorting[0];54const { id, desc } = sort;55dbData.sort((a, b) => {56if (desc) {57return a[id] < b[id] ? 1 : -1;58}59return a[id] > b[id] ? 1 : -1;60});61}6263res.status(200).json({64data:65dbData?.slice(parseInt(start), parseInt(start) + parseInt(size)) ?? [],66meta: { totalRowCount: dbData.length },67});68}
View Extra Storybook Examples