<CreateBase>
<CreateBase>
is a headless component that prepares a form submit handler, and renders its children in a CreateContext
. Use it to build a custom creation page layout.
<CreateBase>
relies on the useCreateController
hook.
Use <CreateBase>
to create a custom Creation view, with exactly the content you add as child and nothing else (no title, card, or list of actions).
import * as React from "react";import { CreateBase, Form } from "ra-core";import { TextInput } from './TextInput';import { SelectInput } from './SelectInput';
export const BookCreate = () => ( <CreateBase> <div> <h1>Book Creation</h1> <div> <Form> <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" }, ]} /> </Form> </div> </div> </CreateBase>);
You can customize the <CreateBase>
component using the following props:
children
: the components that renders the formrender
: alternative to children, a function that takes theCreateController
context and renders the formdisableAuthentication
: disable the authentication checkmutationMode
: Switch to optimistic or undoable mutations (pessimistic by default)mutationOptions
: options for thedataProvider.create()
callrecord
: initialize the form with a recordredirect
: change the redirect location after successful creationresource
: override the name of the resource to createtransform
: transform the form data before callingdataProvider.create()
children
Section titled “children”The <CreateBase>
component will render its children inside a CreateContext
. Children can be any React node, but are usually a form component like the headless <Form>
component.
import { CreateBase, Form } from 'ra-core';import { TextInput } from './TextInput';import { DateInput } from './DateInput';
export const PostCreate = () => ( <CreateBase> <Form> <TextInput source="title" /> <TextInput source="author" /> <DateInput source="published_at" defaultValue={new Date().toISOString().split('T')[0]} /> </Form> </CreateBase>);
Tip: Alternatively to children
, you can pass a render
prop to <CreateBase>
.
disableAuthentication
Section titled “disableAuthentication”By default, the <CreateBase>
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 creation page, set the disableAuthentication
prop to true
.
const PostCreate = () => ( <CreateBase disableAuthentication> <Form> {/* form content */} </Form> </CreateBase>);
mutationMode
Section titled “mutationMode”The <CreateBase>
view exposes a Save button, which perform a “mutation” (i.e. it creates the data). Ra-core offers three modes for mutations. The mode determines when the side effects (redirection, notifications, etc.) are executed:
pessimistic
(default): 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
: 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 <CreateBase>
use the pessimistic
mutation mode as the new record identifier is often generated on the backend. However, should you decide to generate this identifier client side, you can change the mutationMode
to either optimistic
or undoable
:
const PostCreate = () => ( <CreateBase mutationMode="optimistic" transform={data => ({ id: generateId(), ...data })}> <Form> {/* form content */} </Form> </CreateBase>);
And to make the record creation undoable:
const PostCreate = () => ( <CreateBase mutationMode="undoable" transform={data => ({ id: generateId(), ...data })}> <Form> {/* form content */} </Form> </CreateBase>);
mutationOptions
Section titled “mutationOptions”You can customize the options you pass to react-query’s useMutation
hook, e.g. to pass a custom meta
to the dataProvider.create()
call.
import { CreateBase, Form } from 'ra-core';
const PostCreate = () => ( <CreateBase mutationOptions={{ meta: { foo: 'bar' } }}> <Form> {/* form content */} </Form> </CreateBase>);
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, ra-core shows a notification, and redirects to the new record edit 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, useRedirect, CreateBase, Form } from 'ra-core';
const PostCreate = () => { const notify = useNotify(); const redirect = useRedirect();
const onSuccess = (data) => { notify(`Changes saved`); redirect(`/posts/${data.id}`); };
return ( <CreateBase mutationOptions={{ onSuccess }}> <Form> {/* form content */} </Form> </CreateBase> );}
Similarly, you can override the failure side effects with an onError
option. By default, when the save action fails at the dataProvider level, ra-core shows an error notification.
import * as React from 'react';import { useNotify, CreateBase, Form } from 'ra-core';
const PostCreate = () => { const notify = useNotify();
const onError = (error) => { notify(`Could not create post: ${error.message}`); };
return ( <CreateBase mutationOptions={{ onError }}> <Form> {/* form content */} </Form> </CreateBase> );}
record
Section titled “record”The record
prop allows to initialize the form with non-empty values. It is exposed for consistency with the EditBase component, but if you need default values, you should use the defaultValues
prop on the Form element instead.
const PostCreate = () => ( <CreateBase record={{ title: 'Default title' }}> <Form> {/* form content */} </Form> </CreateBase>);
redirect
Section titled “redirect”By default, submitting the form in the <CreateBase>
view redirects to the Edit view.
You can customize the redirection by setting the redirect
prop to one of the following values:
'edit'
: redirect to the Edit view (the default)'list'
: redirect to the List view'show'
: redirect to the Show viewfalse
: do not redirect- A function
(resource, id, data) => string
to redirect to different targets depending on the record
const PostCreate = () => ( <CreateBase redirect="list"> <Form> {/* form content */} </Form> </CreateBase>);
Note that the redirect
prop is ignored if you set the mutationOptions
prop. See that prop for how to set a different redirection path in that case.
render
Section titled “render”Alternatively to children
, you can pass a render
prop to <CreateBase>
. It will receive the CreateContext
as its argument, and should return a React node.
This allows to inline the render logic for the create page.
import { CreateBase, Form } from 'ra-core';import { TextInput } from './TextInput';import { DateInput } from './DateInput';
const PostCreate = () => ( <CreateBase render={({ save, saving }) => ( <div> <h1>Create new Post</h1> <Form onSubmit={save}> <TextInput source="title" /> <TextInput source="teaser" multiline /> <TextInput source="body" multiline /> <DateInput source="published_at" defaultValue={new Date().toISOString().split('T')[0]} /> <button type="submit" disabled={saving}> {saving ? 'Saving...' : 'Save'} </button> </Form> </div> )} />);
Tip: When receiving a render
prop, the <CreateBase>
component will ignore the children
prop.
resource
Section titled “resource”Components based on <CreateBase>
are often used as <Resource create>
props, and therefore rendered when the URL matches /[resource]/create
. The <CreateBase>
component generates a call to dataProvider.create()
using the resource name from the URL by default.
You can decide to use a <CreateBase>
component in another path, or embedded in a page using another resource name (e.g. in a Dialog). In that case, you can explicitly set the resource
name:
const PostCreate = () => ( <CreateBase resource="posts"> <Form> {/* form content */} </Form> </CreateBase>);
transform
Section titled “transform”To transform a record after the user has submitted the form but before the record is passed to dataProvider.create()
, use the transform
prop. It expects a function taking a record as argument, and returning a modified record. For instance, to add a computed field upon creation:
export const UserCreate = () => { const transform = data => ({ ...data, fullName: `${data.firstName} ${data.lastName}` }); return ( <CreateBase transform={transform}> <Form> {/* form content */} </Form> </CreateBase> );}
The transform
function can also return a Promise
, which allows you to do all sorts of asynchronous calls (e.g. to the dataProvider
) during the transformation.
Security
Section titled “Security”The <CreateBase>
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, <CreateBase>
will only render if the user has the “create” access to the related resource.
For instance, for the <PostCreate>
page below:
import { CreateBase, Form } from 'ra-core';import { TextInput } from './TextInput';
// Resource name is "posts"const PostCreate = () => ( <CreateBase> <Form> <TextInput source="title" /> <TextInput source="author" /> <TextInput source="published_at" /> </Form> </CreateBase>);
<CreateBase>
will call authProvider.canAccess()
using the following parameters:
{ action: "create", resource: "posts" }
Users without access will be redirected to the Access Denied page.
Note: Access control is disabled when you use the disableAuthentication
prop.
Prefilling the Form
Section titled “Prefilling the Form”You sometimes need to pre-populate a record based on a related record. For instance, to create a comment related to an existing post.
By default, the <CreateBase>
view starts with an empty record
. However, if the location
object (injected by react-router-dom) contains a record
in its state
, the <CreateBase>
view uses that record
instead of the empty object. That’s how create buttons with pre-filled data work.
That means that if you want to create a link to a creation form, presetting some values, all you have to do is to set the state
when navigating to the create route:
import * as React from 'react';import { useNavigate } from 'react-router-dom';import { useRecordContext } from 'ra-core';
const CreateRelatedCommentButton = () => { const record = useRecordContext(); const navigate = useNavigate();
const handleClick = () => { navigate('/comments/create', { state: { record: { post_id: record.id } } }); };
return ( <button onClick={handleClick}> Create Related Comment </button> );};
Tip: The <CreateBase>
component also watches the “source” parameter of location.search
(the query string in the URL) in addition to location.state
(a cross-page message hidden in the router memory). So the CreateRelatedCommentButton
could also be written as:
import * as React from 'react';import { useNavigate } from 'react-router-dom';import { useRecordContext } from 'ra-core';
const CreateRelatedCommentButton = () => { const record = useRecordContext(); const navigate = useNavigate();
const handleClick = () => { navigate(`/comments/create?source=${JSON.stringify({ post_id: record.id })}`); };
return ( <button onClick={handleClick}> Create Related Comment </button> );};
Should you use the location state
or the location search
? The latter modifies the URL, so it’s only necessary if you want to build cross-application links (e.g. from one admin to the other). In general, using the location state
is a safe bet.
And if you want to prefill the form with constant values, use the defaultValues
prop on the Form component.
You can detect prefilled values by leveraging the useRecordFromLocation
hook:
import { CreateBase, Form, useRecordFromLocation } from 'ra-core';import { TextInput } from './TextInput';
const PostCreate = () => { const recordFromLocation = useRecordFromLocation();
return ( <CreateBase> {recordFromLocation && ( <div style={{ padding: '12px 16px', backgroundColor: '#e3f2fd', border: '1px solid #2196f3', borderRadius: '4px', marginBottom: '16px', color: '#0d47a1' }} > Some fields have been pre-filled from the referring page. </div> )} <Form> <div style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}> <TextInput source="title" /> <TextInput source="author" /> <TextInput source="post_id" /> </div> </Form> </CreateBase> );};