useListContext

Whenever react-admin displays a List, it creates a ListContext to store the list data, as well as filters, pagination, sort state, and callbacks to update them.

The ListContext is available to descendants of:

  • <List>,
  • <ListGuesser>,
  • <ListBase>,
  • <ReferenceArrayField>,
  • <ReferenceManyField>

All descendant components can therefore access the list context, using the useListContext hook. As a matter of fact, react-admin’s <Datagrid>, <FilterForm>, and <Pagination> components all use the useListContext hook.

Usage

Call useListContext in a component, then use this component as a descendant of a List component.

// in src/posts/Aside.js
import { Typography } from '@mui/material';
import { useListContext } from 'react-admin';

export const Aside = () => {
    const { data, isLoading } = useListContext();
    if (isLoading) return null;
    return (
        <div>
            <Typography variant="h6">Posts stats</Typography>
            <Typography variant="body2">
                Total views: {data.reduce((sum, post) => sum + post.views, 0)}
            </Typography>
        </div>
    );
};

// in src/posts/PostList.js
import { List, Datagrid, TextField } from 'react-admin';
import Aside from './Aside';

export const PostList = () => (
    <List aside={<Aside />}>
        <Datagrid>
            <TextField source="id" />
            <TextField source="title" />
            <TextField source="views" />
        </Datagrid>
    </List>
);

Return Value

useListContext returns an object with the following keys:

const {
    // Data
    data, // Array of the list records, e.g. [{ id: 123, title: 'hello world' }, { ... }
    total, // Total number of results for the current filters, excluding pagination. Useful to build the pagination controls, e.g. 23      
    isFetching, // Boolean, true while the data is being fetched, false once the data is fetched
    isLoading, // Boolean, true until the data is available for the first time
    // Pagination
    page, // Current page. Starts at 1
    perPage, // Number of results per page. Defaults to 25
    setPage, // Callback to change the page, e.g. setPage(3)
    setPerPage, // Callback to change the number of results per page, e.g. setPerPage(25)
    hasPreviousPage, // Boolean, true if the current page is not the first one
    hasNextPage, // Boolean, true if the current page is not the last one
    // Sorting
    sort, // Sort object { field, order }, e.g. { field: 'date', order: 'DESC' }
    setSort, // Callback to change the sort, e.g. setSort({ field: 'name', order: 'ASC' })
    // Filtering
    filterValues, // Dictionary of filter values, e.g. { title: 'lorem', nationality: 'fr' }
    displayedFilters, // Dictionary of displayed filters, e.g. { title: true, nationality: true }
    setFilters, // Callback to update the filters, e.g. setFilters(filters, displayedFilters)
    showFilter, // Callback to show one of the filters, e.g. showFilter('title', defaultValue)
    hideFilter, // Callback to hide one of the filters, e.g. hideFilter('title')
    // Record selection
    selectedIds, // Array listing the ids of the selected records, e.g. [123, 456]
    onSelect, // Callback to change the list of selected records, e.g. onSelect([456, 789])
    onToggleItem, // Callback to toggle the record selection for a given id, e.g. onToggleItem(456)
    onUnselectItems, // Callback to clear the record selection, e.g. onUnselectItems();
    // Misc
    defaultTitle, // Translated title based on the resource, e.g. 'Posts'
    resource, // Resource name, deduced from the location. e.g. 'posts'
    refetch, // Callback for fetching the list data again
} = useListContext();

Declarative Version

useListContext often forces you to create a new component just to access the list context. If you prefer a declarative approach based on render props, you can use the <WithListContext> component instead:

import { WithListContext } from 'react-admin';
import { Typography } from '@mui/material';

export const Aside = () => (
    <WithListContext render={({ data, isLoading }) => 
        !isLoading && (
            <div>
                <Typography variant="h6">Posts stats</Typography>
                <Typography variant="body2">
                    Total views: {data.reduce((sum, post) => sum + post.views, 0)}
                </Typography>
            </div>
    )} />
);

Using setFilters to Update Filters

The setFilters method is used to update the filters. It takes three arguments:

  • filters: an object containing the new filter values
  • displayedFilters: an object containing the new displayed filters
  • debounced: set to false to disable the debounce (true by default)

You can use it to update the filters in a custom filter component:

import { useState } from 'react';
import { useListContext } from 'react-admin';

const CustomFilter = () => {
    const { filterValues, setFilters } = useListContext();
    const [formValues, setFormValues] = useState(filterValues);

    const handleChange = (event) => {
        setFormValues(formValues => ({
            ...formValues,
            [event.target.name]: event.target.value
        }));
    };

    const handleSubmit = (event) => {
        event.preventDefault();
        // The 3rd parameter disables the debounce ⤵
        setFilters(filterFormValues, undefined, false);
    };

    return (
        <form onSubmit={handleSubmit}>
            <input name="country" value={formValues.country} onChange={handleChange} />
            <input name="city" value={formValues.city} onChange={handleChange} />
            <input name="zipcode" value={formValues.zipcode} onChange={handleChange} />
            <input type="submit">Filter</input>
        </form>
    );
};

Beware that setFilters is debounced by default, to avoid making too many requests to the server when using search-as-you-type inputs. In the example above, this is not necessary. That’s why you should set the third argument to setFilters is set to false to disable the debounce.

Disabling the debounce with the third parameter is also necessary when you use setFilters and other list context methods (like setSort) in a single function. Otherwise, the setFilters call would override the other changes.

const changeListParams = () => {
    setSort({ field: 'name', order: 'ASC' });
    // The 3rd parameter disables the debounce     ⤵
    setFilters({ is_published: true }, undefined, false);
};

TypeScript

The useListContext hook accepts a generic parameter for the record type:

import { Typography } from '@mui/material';
import { List, Datagrid, TextField, useListContext } from 'react-admin';

type Post = {
    id: number;
    title: string;
    views: number;
};

export const Aside = () => {
    const { data: posts, isLoading } = useListContext<Post>();
    if (isLoading) return null;
    return (
        <div>
            <Typography variant="h6">Posts stats</Typography>
            <Typography variant="body2">
                {/* TypeScript knows that posts is of type Post[] */}
                Total views: {posts.reduce((sum, post) => sum + post.views, 0)}
            </Typography>
        </div>
    );
};

export const PostList = () => (
    <List aside={<Aside />}>
        <Datagrid>
            <TextField source="id" />
            <TextField source="title" />
            <TextField source="views" />
        </Datagrid>
    </List>
);

Recipes

You can find many usage examples of useListContext in the documentation, including:

Tip: <ReferenceManyField>, as well as other relationship-related components, also implement a ListContext. That means you can use a <Datagrid> of a <Pagination> inside these components!