Skip to content

CRUD Pages

Most admin and B2B apps start with a few basic screens to manipulate records:

  • A list page, including the ability to filter, paginate, and sort the records
  • A read-only page displaying the record details
  • An edition page, allowing to update the record via a form
  • A creation page

We call this type of interface a “CRUD” interface because it allows us to Create, Read, Update, and Delete records.

Ra-core’s headless architecture provides powerful hooks and Base components to build CRUD interfaces with any UI library.

Ra-core provides headless Base components for CRUD operations:

  • <ListBase> fetches and manages a list of records
  • <ShowBase> fetches and manages a single record for display
  • <EditBase> fetches and manages a record for editing
  • <CreateBase> manages the creation of a new record

Each component reads the parameters from the URL, fetches the data from the data provider, stores the data in a context, and renders its child component.

For example, to display a list of posts, you would use the <ListBase> component:

import { ListBase } from 'ra-core';
import { DataTable } from './DataTable';
const PostList = () => (
<ListBase resource="posts">
<DataTable>
<DataTable.Col source="id" />
<DataTable.Col source="title" />
<DataTable.Col source="body" />
</DataTable>
</ListBase>
);

Here, the <ListBase> component will call dataProvider.getList('posts') to fetch the list of posts and create a ListContext to store the data. The <DataTable> component will read the data from that ListContext and render a row for each post. That’s why there is no need to explicitly pass the data to the <DataTable> component.

<ListBase> and other Base components don’t just fetch data; they provide a way to update the page settings:

  • Sort field and order
  • Current page & page size
  • Filters
  • Record selection

The ListContext exposes callbacks to update these settings, which you can use in your list UI component to update the data.

const listContext = useListContext();
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
meta, // Additional information about the list, like facets & statistics
isPending, // Boolean, true until the data is available
isFetching, // Boolean, true while the data is being fetched, false once the data is fetched
isLoading, // Boolean, true until the data is fetched 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
} = listContext;

You could declare the CRUD routes manually using react-router’s <Route> component. But it’s such a typical pattern that ra-core provides a shortcut: the <Resource> component.

<Resource
name="posts"
list={PostList} // maps PostList to /posts
show={PostShow} // maps PostShow to /posts/:id/show
edit={PostEdit} // maps PostEdit to /posts/:id
create={PostCreate} // maps PostCreate to /posts/create
/>

This is the equivalent of the following react-router configuration:

import { ResourceContextProvider } from 'ra-core';
import { Routes, Route } from 'react-router-dom';
<ResourceContextProvider value="posts">
<Routes>
<Route path="/posts" element={<PostList />} />
<Route path="/posts/:id/show" element={<PostShow />} />
<Route path="/posts/:id" element={<PostEdit />} />
<Route path="/posts/create" element={<PostCreate />} />
</Routes>
</ResourceContextProvider>

<Resource> defines a ResourceContext storing the current resource name. This context is used by the <ListBase>, <EditBase>, <CreateBase>, and <ShowBase> components to determine the resource they should fetch. So when declaring page components with <Resource>, you don’t need to pass the resource prop to them.

import { ListBase, useListContext } from 'ra-core';
const PostList = () => (
<ListBase resource="posts">
<ListBase>
<DataTable>
<DataTable.Col source="id" />
<DataTable.Col source="title" />
<DataTable.Col source="body" />
</DataTable>
</ListBase>
);

Check the <Resource> documentation to learn more about routing and resource context.