React Admin v3 Advanced Recipes: Creating a Record Related to the Current One
This is an update of the first article in the series of advanced tutorials for React-admin, updated for v3. It assumes you already understand the basics of react-admin
and have at least completed the official tutorial.
Update 10/24: An update to this article for react-admin v4 is available at React Admin v4 Advanced Recipes: Creating a Record Related to the Current One.
Today, we'll see how to add a button on a show or edit page to create a new resource related to the one displayed. For example, a new comment from the post show or edit pages. The create form should have its post already set when coming from the post page and it should redirect to the original post after creation.
Initializing The Project
For the purpose of this tutorial, we'll use a very simple data model:
- the post resource will only have a title, a teaser and a body
- the comment resource will only have a creation date, a body and a reference to its post
We won't go into the details of all the components here as they are pretty straightforward but you can explore the code in this codesandbox.
Moreover, as we don't want to initialize a real API, we'll be using ra-data-fakerest
as our dataProvider
. It generates a fake API server from an object containing our data. In our case, this object looks like this:
const data = {
posts: [
{
id: 1,
title: "...",
teaser: "...",
body: "<p>...</p>"
},
...
],
comments: [
{
id: 1,
post_id: 6,
body: "...",
created_at: new Date("2012-08-02")
},
...
]
}
ra-data-fakerest
will fake an API returning JSON data for routes such as:
- GET
/posts
, - GET
/posts/1
- PUT
/posts/1
- POST
/posts
Creating A New Comment From The Post Show Page
Back to our problem now. How can we set the post when creating a new comment?
React-admin automatically detects if a record was specified in the react-router location state. React-admin initialize the form values based on this location state.
So, we just need to create a link from our post page to the comment creation page, with the correct state.
Let's start with the easiest one, using the actions
prop of the Show
component:
// in src/posts/PostShow.js
// ...
import CardActions from "@material-ui/core/CardActions";
import ChatBubbleIcon from "@material-ui/icons/ChatBubble";
import { Button } from "react-admin";
const AddNewCommentButton = ({ record }) => (
<Button
component={Link}
to={{
pathname: "/comments/create",
// Here we specify the initial record for the create view
state: { record: { post_id: record.id } },
}}
label="Add a comment"
>
<ChatBubbleIcon />
</Button>
);
const PostShowActions = ({ basePath, data }) => (
<CardActions>
<ListButton basePath={basePath} />
<RefreshButton />
<AddNewCommentButton record={data} />
</CardActions>
);
const PostShow = props => (
<Show {...props} actions={<PostShowActions />}>
...
</Show>
);
We now have a button to create a new comment for the current post on our show page. Great!
Moving The Button Inside A Tab
However, this button is displayed on every tab. It would be better to show it only on the comments tab.
We know from the documentation that every field component receives a record
prop containing the current record (here, a post
). Let's remove the actions
prop and add the button below the comment datagrid:
// in src/posts/PostShow.js
// ...
const PostShow = props => (
<Show {...props}>
<TabbedShowLayout>
...
<Tab label="Comments">
<ReferenceManyField
addLabel={false}
reference="comments"
target="post_id"
sort={{ field: "created_at", order: "DESC" }}
>
<Datagrid>
<DateField source="created_at" />
<TextField source="body" />
<EditButton />
</Datagrid>
</ReferenceManyField>
<AddNewCommentButton />
</Tab>
</TabbedShowLayout>
</Show>
);
Now our button is only displayed in the appropriate context, that's better for our users. We just need to style it a bit so that it's not so close to the Datagrid
.
// in src/posts/PostShow.js
// ...
import ChatBubbleIcon from "@material-ui/icons/ChatBubble";
import { makeStyles } from "@material-ui/core/styles";
import { Button } from "react-admin";
const useStyles = makeStyles(theme => ({
button: {
marginTop: theme.spacing(1),
marginBottom: theme.spacing(1)
},
}));
const AddNewCommentButton = ({ record }) => {
const classes = useStyles();
return (
<Button
className={classes.button}
variant="contained"
component={Link}
to={{
pathname: "/comments/create",
// Here we specify the initial record for the create view
state: { record: { post_id: record.id } },
}}
label="Add a comment"
>
<ChatBubbleIcon />
</Button>
);
};
Hooray! We're done! Or are we? When submitting the new comment, we're redirected to its edit page. Wouldn't it be better to redirect our user to the comments tab of the post?
Redirecting To The Comments Tab Of The Post
The SimpleForm
component accepts a redirect prop which can be edit
, show
, list
, or our very own path:
// ...
import { useLocation } from 'react-router';
const CommentCreate = props => {
// Read the post_id from the location
const location = useLocation();
const post_id =
location.state && location.state.record
? location.state.record.post_id
: undefined;
const redirect = post_id ? `/posts/${post_id}/show/comments` : false;
return (
<Create {...props}>
<SimpleForm
initialValues={{ created_at: today }}
redirect={redirect}
>
// ...
</SimpleForm>
</Create>
);
};
And voila!
Conclusion
Hopefully this article proved that not only does react-admin speeds up admin interfaces development, it also offers all that's necessary to customize the resulting UI. Morever, It's just Reactâ„¢, It's just JavaScriptâ„¢ !
You can explore the full code for this article in this codesandbox