<DeletedRecordsList>

The <DeletedRecordsList> component fetches a list of deleted records from the data provider and display them in a <DataTable> with pagination, filters and sort.

The rendered <DataTable> includes buttons for restoring or permanently deleting the deleted records, and allows to show the deleted record data in a dialog when clicking on a row.

A deleted records list

Usage

<DeletedRecordsList> uses dataProvider.getListDeleted() to get the deleted records to display, so in general it doesn’t need any property. However, you need to define the route to reach this component manually using <CustomRoutes>.

// in src/App.js
import { Admin, CustomRoutes } from 'react-admin';
import { Route } from 'react-router-dom';
import { DeletedRecordsList } from '@react-admin/ra-soft-delete';

export const App = () => (
    <Admin>
        ...
        <CustomRoutes>
            <Route path="/deleted" element={<DeletedRecordsList />} />
        </CustomRoutes>
    </Admin>
);

That’s enough to display the deleted records list, with functional simple filters, sort and pagination.

Props

Prop Required Type Default Description
debounce Optional number 500 The debounce delay in milliseconds to apply when users change the sort or filter parameters.
children Optional Element <Deleted Records Table> The component used to render the list of deleted records.
detail Components Optional Record<string, ComponentType> - The custom show components for each resource in the deleted records list.
disable Authentication Optional boolean false Set to true to disable the authentication check.
disable SyncWithLocation Optional boolean false Set to true to disable the synchronization of the list parameters with the URL.
filter Optional object - The permanent filter values.
filter DefaultValues Optional object - The default filter values.
mutation Mode Optional string 'undoable' Mutation mode ('undoable', 'pessimistic' or 'optimistic').
pagination Optional ReactElement <Pagination> The pagination component to use.
perPage Optional number 10 The number of records to fetch per page.
queryOptions Optional object - The options to pass to the useQuery hook.
resource Optional string - The resource of deleted records to fetch and display
sort Optional object { field: 'deleted_at', order: 'DESC' } The initial sort parameters.
storeKey Optional string or false - The key to use to store the current filter & sort. Pass false to disable store synchronization
title Optional string | ReactElement | false - The title to display in the App Bar.
sx Optional object - The CSS styles to apply to the component.

children

By default, <DeletedRecordsList> renders a <DeletedRecordsTable> component that displays the deleted records in a <DataTable>, with buttons to restore or permanently delete them. You can customize this table by passing custom children.

import { DataTable } from 'react-admin';
import { DeletedRecordsList } from '@react-admin/ra-soft-delete';

export const CustomDeletedRecords = () => (
    <DeletedRecordsList>
        <DataTable>
            <DataTable.Col source="id" />
            <DataTable.Col source="resource" />
            <DataTable.Col source="deleted_at" />
            <DataTable.Col source="deleted_by" />
            <DataTable.Col source="data.title" label="Title" />
        </DataTable>
    </DeletedRecordsList>
);

debounce

By default, <DeletedRecordsList> does not refresh the data as soon as the user enters data in the filter form. Instead, it waits for half a second of user inactivity (via lodash.debounce) before calling the dataProvider on filter change. This is to prevent repeated (and useless) calls to the API.

You can customize the debounce duration in milliseconds - or disable it completely - by passing a debounce prop to the <DeletedRecordsList> component:

// wait 1 seconds instead of 500 milliseconds befoce calling the dataProvider
const DeletedRecordsWithDebounce = () => <DeletedRecordsList debounce={1000} />;

detailComponents

By default, <DeletedRecordsList> will show the deleted records data on click on a row of the <DataTable> in a <ShowGuesser>.

If you wish to customize the content in this show dialog, you can use the detailComponents prop to customize the dialog content for every resource in the list. The content is the same as a classic <Show> page.

However, you must use <ShowDeleted> component instead of <Show> to write a custom view for a deleted record. This is because <Show> gets a fresh version of the record from the data provider to display it, which is not possible in the deleted records list as the record is now deleted.

import { Admin, CustomRoutes, SimpleShowLayout, TextField } from 'react-admin';
import { Route } from 'react-router-dom';
import { DeletedRecordsList, ShowDeleted } from '@react-admin/ra-soft-delete';

const ShowDeletedBook = () => (
    <ShowDeleted>
        <SimpleShowLayout>
            <TextField source="title" />
            <TextField source="description" />
        </SimpleShowLayout>
    </ShowDeleted>
);

export const App = () => (
    <Admin>
        ...
        <CustomRoutes>
            <Route path="/deleted" element={
                <DeletedRecordsList detailComponents={{
                    books: ShowDeletedBook,
                }} />
            } />
        </CustomRoutes>
    </Admin>
);

disableAuthentication

By default, <DeletedRecordsList> requires the user to be authenticated - any anonymous access redirects the user to the login page.

If you want to allow anonymous access to the deleted records list page, set the disableAuthentication prop to true.

const AnonymousDeletedRecords = () => <DeletedRecordsList disableAuthentication />;

disableSyncWithLocation

By default, react-admin synchronizes the <DeletedRecordsList> parameters (sort, pagination, filters) with the query string in the URL (using react-router location) and the Store.

You may want to disable this synchronization to keep the parameters in a local state, independent for each <DeletedRecordsList> instance. To do so, pass the disableSyncWithLocation prop. The drawback is that a hit on the “back” button doesn’t restore the previous parameters.

const DeletedRecordsWithoutSyncWithLocation = () => <DeletedRecordsList disableSyncWithLocation />;

Tip: disableSyncWithLocation also disables the persistence of the deleted records list parameters in the Store by default. To enable the persistence of the deleted records list parameters in the Store, you can pass a custom storeKey prop.

const DeletedRecordsSyncWithStore = () => <DeletedRecordsList disableSyncWithLocation storeKey="deletedRecordsListParams" />;

filter: Permanent Filter

You can choose to always filter the list, without letting the user disable this filter - for instance to display only published posts. Write the filter to be passed to the data provider in the filter props:

const DeletedPostsList = () => (
    <DeletedRecordsList filter={{ resource: 'posts' }} />
);

The actual filter parameter sent to the data provider is the result of the combination of the user filters (the ones set through the filters component form), and the permanent filter. The user cannot override the permanent filters set by way of filter.

filterDefaultValues

To set default values to filters, you can pass an object literal as the filterDefaultValues prop of the <DeletedRecordsList> element.

const CustomDeletedRecords = () => (
    <DeletedRecordsList filterDefaultValues={{ resource: 'posts' }} />
);

Tip: The filter and filterDefaultValues props have one key difference: the filterDefaultValues can be overridden by the user, while the filter values are always sent to the data provider. Or, to put it otherwise:

const filterSentToDataProvider = { ...filterDefaultValues, ...filterChosenByUser, ...filter };

mutationMode

The <DeletedRecordsList> list exposes restore and delete permanently buttons, which perform “mutations” (i.e. they alter the data). React-admin offers three modes for mutations. The mode determines when the side effects (redirection, notifications, etc.) are executed:

  • pessimistic: The mutation is passed to the dataProvider first. When the dataProvider returns successfully, the mutation is applied locally, and the side effects are executed.
  • optimistic: The mutation is applied locally and the side effects are executed immediately. Then the mutation is passed to the dataProvider. If the dataProvider returns successfully, nothing happens (as the mutation was already applied locally). If the dataProvider returns in error, the page is refreshed and an error notification is shown.
  • undoable (default): The mutation is applied locally and the side effects are executed immediately. Then a notification is shown with an undo button. If the user clicks on undo, the mutation is never sent to the dataProvider, and the page is refreshed. Otherwise, after a 5 seconds delay, the mutation is passed to the dataProvider. If the dataProvider returns successfully, nothing happens (as the mutation was already applied locally). If the dataProvider returns in error, the page is refreshed and an error notification is shown.

By default, <DeletedRecordsList> uses the undoable mutation mode. This is part of the “optimistic rendering” strategy of react-admin; it makes user interactions more reactive.

You can change this default by setting the mutationMode prop - and this affects all buttons in deleted records table. For instance, to remove the ability to undo the changes, use the optimistic mode:

const OptimisticDeletedRecords = () => (
    <DeletedRecordsList mutationMode="optimistic" />
);

And to make the actions blocking, and wait for the dataProvider response to continue, use the pessimistic mode:

const PessimisticDeletedRecords = () => (
    <DeletedRecordsList mutationMode="pessimistic" />
);

Tip: When using any other mode than undoable, the <DeletePermanentlyButton> and <RestoreButton> display a confirmation dialog before calling the dataProvider.

pagination

By default, the <DeletedRecordsList> view displays a set of pagination controls at the bottom of the list.

The pagination prop allows to replace the default pagination controls by your own.

import { Pagination } from 'react-admin';
import { DeletedRecordsList } from '@react-admin/ra-soft-delete';

const DeletedRecordsPagination = () => <Pagination rowsPerPageOptions={[10, 25, 50, 100]} />;

export const DeletedRecordsWithCustomPagination = () => (
    <DeletedRecordsList pagination={<DeletedRecordsPagination />} />
);

See Paginating the List for details.

perPage

By default, the deleted records list paginates results by groups of 10. You can override this setting by specifying the perPage prop:

const DeletedRecordsWithCustomPagination = () => <DeletedRecordsList perPage={25} />;

Note: The default pagination component’s rowsPerPageOptions includes options of 5, 10, 25 and 50. If you set your deleted records list perPage to a value not in that set, you must also customize the pagination so that it allows this value, or else there will be an error.

const DeletedRecordsWithCustomPagination = () => (
-   <DeletedRecordsList perPage={6} />
+   <DeletedRecordsList perPage={6} pagination={<Pagination rowsPerPageOptions={[6, 12, 24, 36]} />} />
);

queryOptions

<DeletedRecordsList> accepts a queryOptions prop to pass query options to the react-query client. Check react-query’s useQuery documentation for the list of available options.

This can be useful e.g. to pass a custom meta to the dataProvider.getListDeleted() call.

import { DeletedRecordsList } from '@react-admin/ra-soft-delete';

const CustomDeletedRecords = () => (
    <DeletedRecordsList queryOptions={{ meta: { foo: 'bar' } }} />
);

With this option, react-admin will call dataProvider.getListDeleted() on mount with the meta: { foo: 'bar' } option.

You can also use the queryOptions prop to override the default error side effect. By default, when the dataProvider.getListDeleted() call fails, react-admin shows an error notification. Here is how to show a custom notification instead:

import { useNotify, useRedirect } from 'react-admin';
import { DeletedRecordsList } from '@react-admin/ra-soft-delete';

const CustomDeletedRecords = () => {
    const notify = useNotify();
    const redirect = useRedirect();

    const onError = (error) => {
        notify(`Could not load list: ${error.message}`, { type: 'error' });
        redirect('/dashboard');
    };

    return (
        <DeletedRecordsList queryOptions={{ onError }} />
    );
}

The onError function receives the error from the dataProvider call (dataProvider.getListDeleted()), which is a JavaScript Error object (see the dataProvider documentation for details).

resource

<DeletedRecordsList> fetches the deleted records from the data provider using the dataProvider.getListDeleted() method. When no resource is specified, it will fetch all deleted records from all resources and display a filter.

If you want to display only the deleted records of a specific resource, you can pass the resource prop:

const DeletedPosts = () => (
    <DeletedRecordsList resource="posts" />
);

When a resource is specified, the filter will not be displayed, and the list will only show deleted records of that resource.

The title is also updated accordingly. Its translation key is ra-soft-delete.deleted_records_list.resource_title.

sort

Pass an object literal as the sort prop to determine the default field and order used for sorting:

const PessimisticDeletedRecords = () => (
    <DeletedRecordsList sort={{ field: 'id', order: 'ASC' }} />
);

sort defines the default sort order ; the list remains sortable by clicking on column headers.

For more details on list sort, see the Sorting The List section.

storeKey

By default, react-admin stores the list parameters (sort, pagination, filters) in localStorage so that users can come back to the list and find it in the same state as when they left it. The <DeletedRecordsList> component uses a specific identifier to store the list parameters under the key ra-soft-delete.listParams.

If you want to use multiple <DeletedRecordsList> and keep distinct store states for each of them (filters, sorting and pagination), you must give each list a unique storeKey property. You can also disable the persistence of list parameters and selection in the store by setting the storeKey prop to false.

In the example below, the deleted records lists store their list parameters separately (under the store keys 'deletedBooks' and 'deletedAuthors'). This allows to use both components in the same app, each having its own state (filters, sorting and pagination).

import { Admin, CustomRoutes } from 'react-admin';
import { Route } from 'react-router-dom';
import { DeletedRecordsList } from '@react-admin/ra-soft-delete';

const Admin = () => {
    return (
        <Admin dataProvider={dataProvider}>
            <CustomRoutes>
                <Route path="/books/deleted" element={
                    <DeletedRecordsList filter={{ resource: 'books' }} storeKey="deletedBooks" />
                } />
                <Route path="/authors/deleted" element={
                    <DeletedRecordsList filter={{ resource: 'authors' }} storeKey="deletedAuthors" />
                } />
            </CustomRoutes>
            <Resource name="books" />
        </Admin>
    );
};

Tip: The storeKey is actually passed to the underlying useDeletedRecordsListController hook, which you can use directly for more complex scenarios. See the useDeletedRecordsListController doc for more info.

Note: Selection state will remain linked to a global key regardless of the specified storeKey string. This is a design choice because if row selection is not stored globally, then when a user permanently deletes or restores a record it may remain selected without any ability to unselect it. If you want to allow custom storeKey’s for managing selection state, you will have to implement your own useDeletedRecordsListController hook and pass a custom key to the useRecordSelection hook. You will then need to implement your own delete buttons to manually unselect rows when deleting or restoring records. You can still opt out of all store interactions including selection if you set it to false.

title

The default title for a list view is the translation key ra-soft-delete.deleted_records_list.title.

You can also customize this title by specifying a custom title prop:

const DeletedRecordsWithTitle = () => <DeletedRecordsList title="Beautiful Trash" />;

The title can be a string, a React element, or false to disable the title.

sx: CSS API

The <DeletedRecordsList> component accepts the usual className prop, but you can override many class names injected to the inner components by React-admin thanks to the sx property (see the sx documentation for syntax and examples). This property accepts the following subclasses:

Rule name Description
& .RaDeletedRecordsList-filters Applied to the filters container
& .RaDeletedRecordsList-table Applied to the <DataTable>
& .RaDeletedRecordsList-dialog Applied to the dialog shown when clicking on a row

For example:

const BeautifulDeletedRecordsList = () => (
    <DeletedRecordsList
        sx={{
            backgroundColor: 'yellow',
            '& .RaDeletedRecordsList-filters': {
                backgroundColor: 'red',
            },
        }}
    />
);

Tip: The <DeletedRecordsList> component classes can also be customized for all instances of the component with its global css name RaDeletedRecordsList as described here.

Access Control

If your authProvider implements Access Control, <DeletedRecordsList> will only be shown if the user has the "deleted_records" access on the virtual "ra-soft-delete" resource.

<DeletedRecordsList> will call authProvider.canAccess() using the following parameters:

{ action: "list_deleted_records", resource: "ra-soft-delete" }

Users without access will be redirected to the Access Denied page.

The permission action for the restore button is "restore", which means that authProvider.canAccess() will be called with the following parameters:

{ action: "restore", resource: "ra-soft-delete", record: [current record] }

Likewise, the permission action for the delete permanently button is "delete", which means that authProvider.canAccess() will be called with the following parameters:

{ action: "delete", resource: "ra-soft-delete", record: [current record] }