MRT logoMantine React Table

On This Page

    Async Loading Feature Guide

    While you are fetching your data, you may want to show some loading indicators. Mantine React Table has some nice loading UI features built in that look better than a simple spinner.

    This guide is mostly focused on the loading UI features. Make sure to also check out the Remote Data and React Query examples for server-side logic examples.

    Relevant Table Options

    #
    Prop Name
    Type
    Default Value
    More Info Links
    1LoadingOverlayProps | ({ table }) => LoadingOverlayPropsMantine LoadingOverlay Docs
    2ProgressProps | ({ isTopToolbar, table }) => ProgressPropsMantine Progress Docs
    3SkeletonProps | ({ cell, column, row, table }) => SkeletonPropsMantine Skeleton Docs

    Relevant State Options

    #
    State Option
    Type
    Default Value
    More Info Links
    1booleanfalse
    2booleanfalse
    3booleanfalse
    4booleanfalse

    isLoading UI

    Rather than coding your own spinner or loading indicator, you can simply set the isLoading state to true, and Mantine React Table will show a loading overlay with cell skeletons for you. The number of rows that get generated are the same as your initialState.pagination.pageSize option.

    const table = useMantineReactTable({
      columns,
      data, //should fallback to empty array while loading data
      state: { isLoading: true },
    });
    First Name
    Last Name
    Email
    City

    Rows per page

    1-10 of 10

    import '@mantine/core/styles.css';
    import '@mantine/dates/styles.css'; //if using mantine date picker features
    import 'mantine-react-table/styles.css'; //make sure MRT styles were imported in your app root (once)
    import { useMemo } from 'react';
    import { MantineReactTable, type MRT_ColumnDef } from 'mantine-react-table';
    import { Person } from './makeData';
    
    const Example = () => {
      const columns = useMemo<MRT_ColumnDef<Person>[]>(
        () => [
          {
            accessorKey: 'firstName',
            header: 'First Name',
          },
          {
            accessorKey: 'lastName',
            header: 'Last Name',
          },
          {
            accessorKey: 'email',
            header: 'Email',
          },
          {
            accessorKey: 'city',
            header: 'City',
          },
        ],
        [],
      );
    
      return (
        <MantineReactTable
          columns={columns}
          data={[]}
          state={{ isLoading: true }}
        />
      );
    };
    
    export default Example;

    Show Loading Overlay, Skeletons, or Progress Bars Individually

    You can control whether the loading overlay, skeletons, or progress bars show individually by setting the showLoadingOverlay, showSkeletons, and showProgressBars states to true.

    const table = useMantineReactTable({
      columns,
      data: data ?? [],
      state: {
        //using react-query terminology as an example here
        showLoadingOverlay: isFetching && isPreviousData, //fetching next page pagination
        showSkeletons: isLoading, //loading for the first time with no data
        showProgressBars: isSavingUser, //from a mutation
      },
    });

    Customize Loading Components

    You can customize the loading overlay, skeletons, and progress bars by passing props to the mantineLoadingOverlayProps, mantineSkeletonProps, and mantineProgressProps table options.

    First Name
    Last Name
    Email
    City
    DylanMurraydmurray@yopmail.comEast Daphne
    RaquelKohlerrkholer33@yopmail.comColumbus
    ErvinReingerereinger@mailinator.comSouth Linda
    BrittanyMcCulloughbmccullough44@mailinator.comLincoln
    BransonFramibframi@yopmain.comNew York
    KevinKleinkklien@mailinator.comNebraska

    Rows per page

    1-6 of 6

    import '@mantine/core/styles.css';
    import '@mantine/dates/styles.css'; //if using mantine date picker features
    import 'mantine-react-table/styles.css'; //make sure MRT styles were imported in your app root (once)
    import { useEffect, useMemo, useState } from 'react';
    import { MantineReactTable, type MRT_ColumnDef } from 'mantine-react-table';
    import { data, type Person } from './makeData';
    import { Button } from '@mantine/core';
    
    const Example = () => {
      const columns = useMemo<MRT_ColumnDef<Person>[]>(
        () => [
          {
            accessorKey: 'firstName',
            header: 'First Name',
          },
          {
            accessorKey: 'lastName',
            header: 'Last Name',
          },
          {
            accessorKey: 'email',
            header: 'Email',
          },
          {
            accessorKey: 'city',
            header: 'City',
          },
        ],
        [],
      );
    
      const [progress, setProgress] = useState(0);
    
      //simulate random progress for demo purposes
      useEffect(() => {
        const interval = setInterval(() => {
          setProgress((oldProgress) => {
            const newProgress = Math.random() * 20;
            return Math.min(oldProgress + newProgress, 100);
          });
        }, 1000);
        return () => clearInterval(interval);
      }, []);
    
      return (
        <MantineReactTable
          columns={columns}
          data={data}
          mantineProgressProps={({ isTopToolbar }) => ({
            color: 'orange',
            variant: 'determinate', //if you want to show exact progress value
            value: progress, //value between 0 and 100
            style: {
              display: isTopToolbar ? 'block' : 'none', //hide bottom progress bar
            },
          })}
          renderTopToolbarCustomActions={() => (
            <Button onClick={() => setProgress(0)} variant="filled">
              Reset
            </Button>
          )}
          state={{ showProgressBars: true }}
        />
      );
    };
    
    export default Example;

    Full Loading and Server-Side Logic Example

    Here is a copy of the full React Query example.

    First Name
    Last Name
    Address
    State
    Phone Number

    Rows per page

    0-0 of 0

    import '@mantine/core/styles.css';
    import '@mantine/dates/styles.css'; //if using mantine date picker features
    import 'mantine-react-table/styles.css'; //make sure MRT styles were imported in your app root (once)
    import { useMemo, useState } from 'react';
    import {
      MantineReactTable,
      useMantineReactTable,
      type MRT_ColumnDef,
      type MRT_ColumnFiltersState,
      type MRT_PaginationState,
      type MRT_SortingState,
      type MRT_ColumnFilterFnsState,
    } from 'mantine-react-table';
    import { ActionIcon, Tooltip } from '@mantine/core';
    import { IconRefresh } from '@tabler/icons-react';
    import {
      QueryClient,
      QueryClientProvider,
      keepPreviousData,
      useQuery,
    } from '@tanstack/react-query';
    
    type User = {
      firstName: string;
      lastName: string;
      address: string;
      state: string;
      phoneNumber: string;
    };
    
    type UserApiResponse = {
      data: Array<User>;
      meta: {
        totalRowCount: number;
      };
    };
    
    interface Params {
      columnFilterFns: MRT_ColumnFilterFnsState;
      columnFilters: MRT_ColumnFiltersState;
      globalFilter: string;
      sorting: MRT_SortingState;
      pagination: MRT_PaginationState;
    }
    
    //custom react-query hook
    const useGetUsers = ({
      columnFilterFns,
      columnFilters,
      globalFilter,
      sorting,
      pagination,
    }: Params) => {
      //build the URL (https://www.mantine-react-table.com/api/data?start=0&size=10&filters=[]&globalFilter=&sorting=[])
      const fetchURL = new URL(
        '/api/data',
        process.env.NODE_ENV === 'production'
          ? 'https://www.mantine-react-table.com'
          : 'http://localhost:3001',
      );
      fetchURL.searchParams.set(
        'start',
        `${pagination.pageIndex * pagination.pageSize}`,
      );
      fetchURL.searchParams.set('size', `${pagination.pageSize}`);
      fetchURL.searchParams.set('filters', JSON.stringify(columnFilters ?? []));
      fetchURL.searchParams.set(
        'filterModes',
        JSON.stringify(columnFilterFns ?? {}),
      );
      fetchURL.searchParams.set('globalFilter', globalFilter ?? '');
      fetchURL.searchParams.set('sorting', JSON.stringify(sorting ?? []));
    
      return useQuery<UserApiResponse>({
        queryKey: ['users', fetchURL.href], //refetch whenever the URL changes (columnFilters, globalFilter, sorting, pagination)
        queryFn: () => fetch(fetchURL.href).then((res) => res.json()),
        placeholderData: keepPreviousData, //useful for paginated queries by keeping data from previous pages on screen while fetching the next page
        staleTime: 30_000, //don't refetch previously viewed pages until cache is more than 30 seconds old
      });
    };
    
    const Example = () => {
      const columns = useMemo<MRT_ColumnDef<User>[]>(
        () => [
          {
            accessorKey: 'firstName',
            header: 'First Name',
          },
          {
            accessorKey: 'lastName',
            header: 'Last Name',
          },
          {
            accessorKey: 'address',
            header: 'Address',
          },
          {
            accessorKey: 'state',
            header: 'State',
          },
          {
            accessorKey: 'phoneNumber',
            header: 'Phone Number',
          },
        ],
        [],
      );
    
      //Manage MRT state that we want to pass to our API
      const [columnFilters, setColumnFilters] = useState<MRT_ColumnFiltersState>(
        [],
      );
      const [columnFilterFns, setColumnFilterFns] = //filter modes
        useState<MRT_ColumnFilterFnsState>(
          Object.fromEntries(
            columns.map(({ accessorKey }) => [accessorKey, 'contains']),
          ),
        ); //default to "contains" for all columns
      const [globalFilter, setGlobalFilter] = useState('');
      const [sorting, setSorting] = useState<MRT_SortingState>([]);
      const [pagination, setPagination] = useState<MRT_PaginationState>({
        pageIndex: 0,
        pageSize: 10,
      });
    
      //call our custom react-query hook
      const { data, isError, isFetching, isLoading, refetch } = useGetUsers({
        columnFilterFns,
        columnFilters,
        globalFilter,
        pagination,
        sorting,
      });
    
      //this will depend on your API response shape
      const fetchedUsers = data?.data ?? [];
      const totalRowCount = data?.meta?.totalRowCount ?? 0;
    
      const table = useMantineReactTable({
        columns,
        data: fetchedUsers,
        enableColumnFilterModes: true,
        columnFilterModeOptions: ['contains', 'startsWith', 'endsWith'],
        initialState: { showColumnFilters: true },
        manualFiltering: true,
        manualPagination: true,
        manualSorting: true,
        mantineToolbarAlertBannerProps: isError
          ? {
              color: 'red',
              children: 'Error loading data',
            }
          : undefined,
        onColumnFilterFnsChange: setColumnFilterFns,
        onColumnFiltersChange: setColumnFilters,
        onGlobalFilterChange: setGlobalFilter,
        onPaginationChange: setPagination,
        onSortingChange: setSorting,
        renderTopToolbarCustomActions: () => (
          <Tooltip label="Refresh Data">
            <ActionIcon onClick={() => refetch()}>
              <IconRefresh />
            </ActionIcon>
          </Tooltip>
        ),
        rowCount: totalRowCount,
        state: {
          columnFilterFns,
          columnFilters,
          globalFilter,
          isLoading,
          pagination,
          showAlertBanner: isError,
          showProgressBars: isFetching,
          sorting,
        },
      });
    
      return <MantineReactTable table={table} />;
    };
    
    const queryClient = new QueryClient();
    
    const ExampleWithReactQueryProvider = () => (
      //Put this with your other react-query providers near root of your app
      <QueryClientProvider client={queryClient}>
        <Example />
      </QueryClientProvider>
    );
    
    export default ExampleWithReactQueryProvider;