<EditBase>
<EditBase>
is a headless variant of <Edit>
: it fetches a record based on the URL, prepares a form submit handler, and renders its children inside an EditContext
. Use it to build a custom edition page layout.
Contrary to <Edit>
, it does not render the page layout, so no title, no actions, and no <Card>
.
<EditBase>
relies on the useEditController
hook.
Usage
Use <EditBase>
to create a custom Edition view, with exactly the content you add as child and nothing else (no title, Card, or list of actions as in the <Edit>
component).
import { EditBase, SelectInput, SimpleForm, TextInput, Title } from "react-admin";
import { Card, CardContent, Container } from "@mui/material";
export const BookEdit = () => (
<EditBase>
<Container>
<Title title="Book Edition" />
<Card>
<CardContent>
<SimpleForm>
<TextInput source="title" />
<TextInput source="author" />
<SelectInput source="availability" choices={[
{ id: "in_stock", name: "In stock" },
{ id: "out_of_stock", name: "Out of stock" },
{ id: "out_of_print", name: "Out of print" },
]} />
</SimpleForm>
</CardContent>
</Card>
</Container>
</EditBase>
);
Props
Prop | Required | Type | Default | Description |
---|---|---|---|---|
children |
Optional | ReactNode |
The components rendering the record fields | |
render |
Optional | (props: EditControllerResult<RecordType>) => ReactNode |
Alternative to children, a function that takes the EditController context and renders the form | |
disable Authentication |
Optional | boolean |
Set to true to disable the authentication check |
|
id |
Optional | string |
The record identifier. If not provided, it will be deduced from the URL | |
loading |
Optional | ReactNode |
The component to render while checking for authentication and permissions | |
mutationMode |
Optional | undoable |
The mutation mode | |
mutationOptions |
Optional | ReactNode |
The options to pass to the useUpdate hook |
|
offline |
Optional | ReactNode |
The component to render when there is no connectivity and the record isn’t in the cache | |
queryOptions |
Optional | object |
The options to pass to the useGetOne hook |
|
transform |
Optional | string |
Transform the form data before calling dataProvider.update() |
children
<EditBase>
renders its children wrapped by a RecordContext
, so you can use any component that depends on such a context to be defined - including all Inputs components.
For instance, to display several fields in a single line, you can use Material UI’s <Grid>
component:
import { EditBase, Form, DateInput, ReferenceInput, SaveButton, TextInput } from 'react-admin';
import { Grid } from '@mui/material';
const BookEdit = () => (
<EditBase>
<Form>
<Grid container spacing={2} sx={{ margin: 2 }}>
<Grid item xs={12} sm={6}>
<TextInput label="Title" source="title" />
</Grid>
<Grid item xs={12} sm={6}>
<ReferenceInput label="Author" source="author_id" reference="authors">
<TextInput source="name" />
</ReferenceInput>
</Grid>
<Grid item xs={12} sm={6}>
<DateInput label="Publication Date" source="published_at" />
</Grid>
<Grid item xs={12}>
<SaveButton />
</Grid>
</Grid>
</Form>
</EditBase>
);
disableAuthentication
By default, the <EditBase>
component will automatically redirect the user to the login page if the user is not authenticated. If you want to disable this behavior and allow anonymous access to a show page, set the disableAuthentication
prop to true
.
import { EditBase } from 'react-admin';
const PostEdit = () => (
<EditBase disableAuthentication>
...
</EditBase>
);
id
By default, <EditBase>
deduces the identifier of the record to show from the URL path. So under the /posts/123/show
path, the id
prop will be 123
. You may want to force a different identifier. In this case, pass a custom id
prop.
import { EditBase } from 'react-admin';
export const PostEdit = () => (
<EditBase id="123">
...
</EditBase>
);
Tip: Pass both a custom id
and a custom resource
prop to use <EditBase>
independently of the current URL. This even allows you to use more than one <EditBase>
component in the same page.
loading
By default, <EditBase>
renders nothing while checking for authentication and permissions. You can provide your own component via the loading
prop:
import { EditBase } from 'react-admin';
export const PostEdit = () => (
<EditBase loading={<p>Checking for permissions...</p>}>
...
</EditBase>
);
mutationMode
The <EditBase>
component exposes a save method, which perform a “mutation” (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, pages using <EditBase>
use 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 both the Save and Delete buttons. For instance, to remove the ability to undo the changes, use the optimistic
mode:
import { EditBase } from 'react-admin';
const PostEdit = () => (
<EditBase mutationMode="optimistic">
// ...
</EditBase>
);
And to make the Save action blocking, and wait for the dataProvider response to continue, use the pessimistic
mode:
import { EditBase } from 'react-admin';
const PostEdit = () => (
<EditBase mutationMode="pessimistic">
// ...
</EditBase>
);
mutationOptions
<EditBase>
calls dataProvider.update()
via react-query’s useMutation
hook. You can customize the options you pass to this hook, e.g. to pass a custom meta
to the dataProvider.update()
call.
import { EditBase, SimpleForm } from 'react-admin';
const PostEdit = () => (
<EditBase mutationOptions={{ meta: { foo: 'bar' } }}>
<SimpleForm>
...
</SimpleForm>
</EditBase>
);
You can also use mutationOptions
to override success or error side effects, by setting the mutationOptions
prop. Refer to the useMutation documentation in the react-query website for a list of the possible options.
Let’s see an example with the success side effect. By default, when the save action succeeds, react-admin shows a notification, and redirects to the list page. You can override this behavior and pass custom success side effects by providing a mutationOptions
prop with an onSuccess
key:
import * as React from 'react';
import { useNotify, useRefresh, useRedirect, EditBase, SimpleForm } from 'react-admin';
const PostEdit = () => {
const notify = useNotify();
const refresh = useRefresh();
const redirect = useRedirect();
const onSuccess = () => {
notify(`Changes saved`);
redirect('/posts');
refresh();
};
return (
<EditBase mutationOptions={{ onSuccess }}>
<SimpleForm>
...
</SimpleForm>
</EditBase>
);
}
The default onSuccess
function is:
() => {
notify('ra.notification.updated', {
messageArgs: { smart_count: 1 },
undoable: mutationMode === 'undoable'
});
redirect('list', resource, data.id, data);
}
Tip: If you just want to customize the redirect behavior, you can use the redirect
prop instead.
Tip: When you use mutationMode="pessimistic"
, the onSuccess
function receives the response from the dataProvider.update()
call, which is the created/edited record (see the dataProvider documentation for details). You can use that response in the success side effects:
import * as React from 'react';
import { useNotify, useRefresh, useRedirect, EditBase, SimpleForm } from 'react-admin';
const PostEdit = () => {
const notify = useNotify();
const refresh = useRefresh();
const redirect = useRedirect();
const onSuccess = (data) => {
notify(`Changes to post "${data.title}" saved`);
redirect('/posts');
refresh();
};
return (
<EditBase mutationOptions={{ onSuccess }} mutationMode="pessimistic">
<SimpleForm>
...
</SimpleForm>
</EditBase>
);
}
Tip: If you want to have different success side effects based on the button clicked by the user (e.g. if the creation form displays two submit buttons, one to “save and redirect to the list”, and another to “save and display an empty form”), you can set the mutationOptions
prop on the <SaveButton>
component, too.
Similarly, you can override the failure side effects with an onError
option. By default, when the save action fails at the dataProvider level, react-admin shows a notification error.
import * as React from 'react';
import { useNotify, useRefresh, useRedirect, EditBase, SimpleForm } from 'react-admin';
const PostEdit = () => {
const notify = useNotify();
const refresh = useRefresh();
const redirect = useRedirect();
const onError = (error) => {
notify(`Could not edit post: ${error.message}`);
redirect('/posts');
refresh();
};
return (
<EditBase mutationOptions={{ onError }}>
<SimpleForm>
...
</SimpleForm>
</EditBase>
);
}
The onError
function receives the error from the dataProvider.update()
call. It is a JavaScript Error object (see the dataProvider documentation for details).
The default onError
function is:
(error) => {
notify(typeof error === 'string' ? error : error.message || 'ra.notification.http_error', { type: 'error' });
if (mutationMode === 'undoable' || mutationMode === 'pessimistic') {
refresh();
}
}
Tip: If you want to have different failure side effects based on the button clicked by the user, you can set the mutationOptions
prop on the <SaveButton>
component, too.
offline
By default, <EditBase>
renders nothing when there is no connectivity and the record hasn’t been cached yet. You can provide your own component via the offline
prop:
import { EditBase } from 'react-admin';
export const PostEdit = () => (
<EditBase offline={<p>No network. Could not load the post.</p>}>
...
</EditBase>
);
Tip: If the record is in the Tanstack Query cache but you want to warn the user that they may see an outdated version, you can use the <IsOffline>
component:
import { EditBase, IsOffline } from 'react-admin';
export const PostEdit = () => (
<EditBase offline={<p>No network. Could not load the post.</p>}>
<IsOffline>
No network. The post data may be outdated.
</IsOffline>
...
</EditBase>
);
queryOptions
<EditBase>
accepts a queryOptions
prop to pass options to the react-query client.
This can be useful e.g. to override the default error side effect. By default, when the dataProvider.getOne()
call fails at the dataProvider level, react-admin shows an error notification and refreshes the page.
You can override this behavior and pass custom side effects by providing a custom queryOptions
prop:
import * as React from 'react';
import { useNotify, useRefresh, useRedirect, EditBase, SimpleForm } from 'react-admin';
const PostEdit = () => {
const notify = useNotify();
const refresh = useRefresh();
const redirect = useRedirect();
const onError = (error) => {
notify(`Could not load post: ${error.message}`, { type: 'error' });
redirect('/posts');
refresh();
};
return (
<EditBase queryOptions=>
<SimpleForm>
...
</SimpleForm>
</EditBase>
);
}
The onError
function receives the error from the dataProvider call (dataProvider.getOne()
), which is a JavaScript Error object (see the dataProvider documentation for details).
The default onError
function is:
(error) => {
notify('ra.notification.item_doesnt_exist', { type: 'error' });
redirect('list', resource);
refresh();
}
render
Alternatively, you can pass a render
function prop instead of children. This function will receive the EditContext
as argument.
import { EditBase, Form, DateInput, ReferenceInput, SaveButton, TextInput } from 'react-admin';
const BookEdit = () => (
<EditBase render={({ isPending, error }) => {
if (isPending) {
return <p>Loading...</p>;
}
if (error) {
return (
<p className="error">
{error.message}
</p>
);
}
return (
<Form>
<Grid container spacing={2} sx={{ margin: 2 }}>
<Grid item xs={12} sm={6}>
<TextInput label="Title" source="title" />
</Grid>
<Grid item xs={12} sm={6}>
<ReferenceInput label="Author" source="author_id" reference="authors">
<TextInput source="name" />
</ReferenceInput>
</Grid>
<Grid item xs={12} sm={6}>
<DateInput label="Publication Date" source="published_at" />
</Grid>
<Grid item xs={12}>
<SaveButton />
</Grid>
</Grid>
</Form>
);
}}/>
);
resource
By default, <EditBase>
operates on the current ResourceContext
(defined at the routing level), so under the /posts/1/show
path, the resource
prop will be posts
. You may want to force a different resource. In this case, pass a custom resource
prop, and it will override the ResourceContext
value.
import { EditBase } from 'react-admin';
export const UsersEdit = () => (
<EditBase resource="users">
...
</EditBase>
);
Tip: Pass both a custom id
and a custom resource
prop to use <EditBase>
independently of the current URL. This even allows you to use more than one <EditBase>
component in the same page.
Security
The <EditBase>
component requires authentication and will redirect anonymous users to the login page. If you want to allow anonymous access, use the disableAuthentication
prop.
If your authProvider
implements Access Control, <EditBase>
will only render if the user has the “edit” access to the related resource.
For instance, for the <PostEdit>
page below:
import { EditBase, SimpleForm, TextInput } from 'react-admin';
// Resource name is "posts"
const PostEdit = () => (
<EditBase>
<SimpleForm>
<TextInput source="title" />
<TextInput source="author" />
<TextInput source="published_at" />
</SimpleForm>
</EditBase>
);
<EditBase>
will call authProvider.canAccess()
using the following parameters:
{ action: "edit", resource: "posts" }
Users without access will be redirected to the Access Denied page.
Note: Access control is disabled when you use the disableAuthentication
prop.