React-admin: January 2025 Update
Since last October, we've released two minor versions of React-admin, our open-source React framework for single-page applications. Read on to learn about the new features and improvements we've added in 5.3, 5.4, and the Enterprise Edition packages.
In A Nutshell
We'll try to make it short, but there's a lot to cover:
- Built-in Access control and pessimistic auth checks
- Ra-rbac rewritten, built-in access control in form layouts
- Lots of DatagridAG improvements
- FilterLiveForm
- StackedFilter defaultValue and type
- TreeWithDetail filter
- Embedding & Prefetching in dataProvider
- queryOptions and mutation options in many-to-many
- Supabase Guessers
- Easier Auth Provider setup (Google, Auth0, keycloak, etc)
- 25k stars on GitHub
- Search with a keyboard shortcut
- Per-resource success notifications
- SolarLayout RTL
- DateInput supports timezones
- useUpdate returnPromise
Access Control
Great News! The advanced Access Control feature that was part of the Enterprise Edition is now available in the open-source version of React-admin.
YouTube might track you and we would rather have your consent before loading this video.
Access control is an opt-in feature for react-admin 5.3. To enable it, you just need to implement a new authProvider
method called canAccess
. For instance:
import { type AuthProvider } from 'react-admin';
export const authProvider: AuthProvider = {
// ...
canAccess: async ({ resource, action }) => {
const role = localStorage.getItem("role");
if (role === "admin") {
return true;
}
if (role === "reader") {
return action === "list" || action === "show";
}
if (role === "user") {
return ["orders", "customers", "products"].includes(resource);
}
return false;
},
}
React-admin invokes this method whenever it needs to render a restricted component, providing a resource and action string as arguments. The method should return a boolean indicating whether the user can perform the action on the resource.
When access control is enabled, all react-admin page components (<List>
, <Create>
, <Edit>
and <Show>
) will show an "Access Denied" message to unauthorized users. The action buttons (<EditButton>
, <CreateButton>
, <DeleteButton>
, <ShowButton>
, and <ListButton>
) as well as the Menu item component also have built-in access control. They are only displayed if the user can access the corresponding action on the resource.
Additionally, we refactored the ra-rbac
enterprise package to leverage the open-source access control feature. This package provides access control to even more components, such as the <Datagrid>
, <SimpleForm>
or <TabbedShowLayout>
components.
Finally, we revisited @react-admin/ra-form-layout
form components to add access control to advanced form layouts. This includes <AccordionForm
, <LongForm>
and <WizardForm>
.
This powerful feature allows you to implement fine-grained access control using various strategies such as Role-Based Access Control (RBAC), Attribute-Based Access Control (ABAC), or Access Control List (ACL). It's a game-changer for securing your applications!
Under the hood, the addition of Access Control primitives imposed a partial rewrite of the security layer of react-admin. The major change introduced by this rewrite is that all the authentication and authorization checks are now pessimistic, and users will never see a page skeleton or a button for which they don't have access rights. In other terms, with react-admin 5.3, apps are more secure by default.
This is a major change! With access control, securing your admin has never been easier. For more details, check out the Access Control documentation.
List View Improvements
We continue to enhance <DatagridAG>
, an advanced datagrid based on ag-grid. This component offers Excel-like features for efficiently handling large datasets, including infinite scrolling, custom cell editors, and robust filtering options. It's designed to provide a seamless and highly customizable experience for complex data management tasks.
You can now use <DatagridAG>
inside an <InfiniteList>
to load data incrementally as the user scrolls down the list:
Additionally, you can now use any react-admin input as a cell editor, including <ReferenceInput>
, making it easier to edit complex data structures:
Handling relationships in <DatagridAG>
is as simple as using <ReferenceField>
and <ReferenceInput>
as the cell renderer and cell editor:
import { List, ReferenceInput } from 'react-admin';
import { DatagridAG } from '@react-admin/ra-datagrid-ag';
export const CommentList = () => {
const columnDefs = [
{
field: 'author.email',
cellRenderer: <EmailField source="author.email" />,
cellEditor: <TextInput source="author.email" />
},
{
field: 'category_id',
cellRenderer: (
<ReferenceField source="category_id" reference="categories" />
),
cellEditor: (
<ReferenceInput source="category_id" reference="categories" />
),
},
];
return (
<List>
<DatagridAG columnDefs={columnDefs} />
</List>
);
};
React-admin offers various filtering options, including the <FilterList>
sidebar. The new <FilterLiveForm>
component introduced in v5.4, simplifies adding live filtering inputs to a <FilterList>
.
You can use <FilterLiveForm>
anywhere in a ListContext
, and use any input component as a filter:
const BookListAside = () => (
<Card sx={{ order: -1, mr: 2, mt: 6, width: 250, height: 'fit-content' }}>
<CardContent>
<FilterListSection label="Author" icon={<Person2Icon />}>
<FilterLiveForm>
<ReferenceInput source="authorId" reference="authors">
<AutocompleteInput helperText={false} />
</ReferenceInput>
</FilterLiveForm>
</FilterListSection>
</CardContent>
</Card>
);
export const BookList = () => (
<List aside={<BookListAside />}>
<Datagrid>
<TextField source="title" />
<ReferenceField source="authorId" reference="authors" />
<TextField source="year" />
</Datagrid>
</List>
);
Another advanced filtering component, <StackedFilters>
, has also been improved. It now accepts default values at both the filter and operators levels:
import { FiltersConfig } from '@react-admin/ra-form-layout';
import { NumberInput, TextInput } from 'react-admin';
import { MyNumberRangeInput } from './MyNumberRangeInput';
const postListFilters: FiltersConfig = {
views: {
operators: [
{ value: 'eq', label: 'Equals', type: 'single' },
{ value: 'neq', label: 'Not Equals', type: 'single', defaultValue: 0 },
],
input: ({ source }) => <NumberInput source={source} />,
},
title: {
operators: [
{ value: 'eq', label: 'Equals', type: 'single' },
{ value: 'neq', label: 'Not Equals', type: 'single' },
],
input: ({ source }) => <TextInput source={source} />,
defaultValue: 'Lorem Ipsum',
}
};
Finally, <TreeWithDetail>
, a replacement for the <List>
component when the records form tree structures like directories, categories, etc. now accepts a permanent filter. This is especially useful when you store multiple trees in the same table in your backend, for instance if you have one items
resource with a type
field, and you want to display a tree for the "clothing" type:
export const ItemList = () => <TreeWithDetails filter={{ type: 'clothing' }} />;
Data Fetching Improvements
On the data fetching side, we added two mechanisms that should help improve the performance of your application: embedding and prefetching.
Many API backends can embed related records. For instance:
- JSON Server and other REST dialects support the
embed
query parameter - GraphQL supports relationships and embedding by design
Starting with version 5.3, react-admin supports the embed
meta in data provider queries. When receiving a query with the meta.embed
option, the data provider should return the embedded records in the response.
const response = useGetOne('posts', { id: 123, meta: { embed: ['author'] } });
// {
// "data": {
// "id": 123,
// "title": "Hello, world",
// "author_id": 456,
// "author": {
// "id": 456,
// "name": "John Doe"
// }
// }
// }
You can use this feature e.g. to display the author name for a post in a single API request (and without using a <ReferenceField>
):
const PostList = () => (
<List queryOptions={{ meta: { embed: ["author"] } }}>
<Datagrid>
<TextField source="title" />
<TextField source="author.name" />
</Datagrid>
</List>
);
Refer to your data provider’s documentation to verify if it supports this feature. If you’re writing your own data provider, check the Writing a Data Provider documentation for more details.
Similar to embedding, prefetching lets you populate the data provider cache based on the response to a given query and avoid subsequent requests to the API.
The prefetched records must be requested in the meta.prefetch
query option, and returned in the meta.prefetched
key of the data provider response.
const response = useGetOne('posts', { id: 123, meta: { prefetch: ['author']} });
// {
// "data": {
// "id": 123,
// "title": "Hello, world",
// "author_id": 456,
// },
// "meta": {
// "prefetched": {
// "authors": [{ "id": 456, "name": "John Doe" }]
// }
// }
// }
React-admin will use the prefetched data in subsequent queries, like the getMany
query of a <ReferenceField>
:
const PostList = () => (
<List queryOptions={{ meta: { prefetch: ['author'] }}}>
<Datagrid>
<TextField source="title" />
{/** renders without an additional request */}
<ReferenceField source="author_id" reference="users" />
</Datagrid>
</List>
);
Embedding and prefetching are powerful features that can significantly reduce the number of requests to your API and improve the performance of your application. They are especially useful when dealing with complex data structures and relationships.
Another neat addition to list components is the support for queryOptions
and mutationOptions
in <ReferenceManyToManyInput>
, the component for editing many-to-many relationships with a Select or Autocomplete input.
Use these options to pass additional parameters to the data provider when fetching the related records or updating the relationship:
<ReferenceManyToManyInput
reference="venues"
through="performances"
using="band_id,venue_id"
queryOptions={{ meta: { myParameter: 'value' } }}
mutationOptions={{ meta: { myParameter: 'value' } }}
>
{/* ... */}
</ReferenceManyToManyInput>
Ecosystem
We are super fans of Supabase, the open-source backend-as-a-service powered by PostgreSQL. We even built a full-stack CRM template, AtomicCRM, using Supabase.
To improve the developer experience on this platform, we recently introduced powerful guesser components leveraging the OpenAPI schema provided by Supabase. Among them, the <AdminGuesser>
lets you scaffold a fully functional admin with a single line of code:
import { AdminGuesser } from 'ra-supabase';
const App = () => (
<AdminGuesser
instanceUrl={YOUR_SUPABASE_URL}
apiKey={YOUR_SUPABASE_API_KEY}
/>
);
export default App;
This is enough to generate a dataProvider
, an authProvider
, and CRUD pages for all database tables.
The generated admin is fully functional:
- All public tables are listed in the sidebar
- Lists are paginated, sortable, and filterable
- Creating, editing, and deleting records is possible
- Forms use the correct input component based on the field type
- Relationships are displayed as links in show views and as autocomplete inputs in edit views
- Supabase handles authentication
To see it in action, check out this ra-supabase intro video:
YouTube might track you and we would rather have your consent before loading this video.
Next, let's talk about pre-built auth providers for Google, Auth0, and Keycloak. Their setup used to be cumbersome, requiring developers to handle initialization inside effects and states. We improved and simplified the setup, as in the Keycloak provider for instance:
const keycloakClient = new Keycloak({
url: '$KEYCLOAK_URL',
realm: '$KEYCLOAK_REALM',
clientId: '$KEYCLOAK_CLIENT_ID',
});
const authProvider = keycloakAuthProvider(keycloakClient, {
initOptions: { onLoad: 'check-sso' },
});
The setup for other providers is now similar, including ra-auth-google and ra-auth-auth0.
Miscellaneous
Until version 5.3, the success notifications used a generic "element" name:
These notifications can now show a different message for each resource:
To set custom notifications for a given resource, provide translations for the following notification messages:
resources.[resource].notifications.created
resources.[resource].notifications.updated
resources.[resource].notifications.deleted
For instance, to customize the post resource messages:
const customEnglishMessages: TranslationMessages = {
...englishMessages,
resources: {
posts: {
name: 'Post |||| Posts',
// ...
notifications: {
created: 'Post created |||| %{smart_count} posts created',
updated: 'Post updated |||| %{smart_count} posts updated',
deleted: 'Post deleted |||| %{smart_count} posts deleted',
},
},
},
};
We also made a few improvements to existing components:
- The
<DateInput>
component now correctly supports timezones. - The global search input now supports customizable keyboard shortcuts:
- The
<SolarLayout>
component now supports right-to-left locales (RTL):
- Finally, we documented a neat option of the
<useUpdate>
hook:returnPromise
. It makes the update callback return a promise that resolves when the mutation has succeeded and rejects when the mutation has failed. This can be useful if the server changes the record, and you need the updated data to update another record:
const [update] = useUpdate(
'posts',
{ id: record.id, data: { isPublished: true } },
{ returnPromise: true }
);
const [create] = useCreate('auditLogs');
const publishPost = async () => {
try {
const post = await update();
create('auditLogs', { data: { action: 'publish', recordId: post.id, date: post.updatedAt } });
} catch (error) {
// handle error
}
};
Conclusion
These are the highlights of the latest react-admin updates. The release notes list them all, as well as all the recent bug and documentation fixes. The upgrade to react-admin v5.4 is straightforward for any react-admin v5 application, as these versions have no breaking change. Upgrade now to leverage the new features and improvements!
We want to thank all the contributors that helped us in the last few months. Whether they reported bugs, submitted pull requests, or helped other users on the Discord channel, they all contributed to making react-admin better.
We're also thrilled that react-admin now has more than 25k stars on GitHub. Thanks to all the stargazers!