React Admin v4 Advanced Recipes: Creating a Record Related to the Current One

React Admin v4 Advanced Recipes: Creating a Record Related to the Current One

Matthieu Chaffotte
• 4 min read

In this article, we’ll see how to let users create a new record related to the current one (for example, to add a new review for the current product) in a react-admin application. The creation form should have its relationship already set, and it should redirect to the original page after saving.

This tutorial is an update over the same tutorial for react-admin v3, which was itself an update of the same tutorial for react-admin v2.

Prefilling A Creation Form

We’ll leverage the <CreateButton> to link to the review creation page. We also need to preset the product_id field of the new review. We can use the state prop to do so. The <Create> component reads this state and uses it for the form default values.

// in src/products/CreateRelatedReviewButton.ts
import { CreateButton, useRecordContext } from "react-admin";

export const CreateRelatedReviewButton = () => {
  const product = useRecordContext();
  return (
    <CreateButton
      resource="reviews"
      state={{ record: { product_id: product.id } }}
    />
  );
};

Add this new <CreateRelatedReviewButton> to the product edition form. In this example, we’ll add it just after a <ReferenceManyField> that renders all the reviews for the current product:

// in src/products/ProductEdit.tsx
import {
  Edit,
  TabbedForm,
  ReferenceManyField,
  Datagrid,
  DateField,
  starField,
  EditButton,
} from "react-admin";
import { StarRatingField } from "./StarRatingField";
import { CreateRelatedReviewButton } from "./CreateRelatedReviewButton";

const ProductEdit = () => (
  <Edit>
    <TabbedForm>
      // ...
      <TabbedForm.Tab>
        <ReferenceManyField reference="reviews" target="product_id">
          <Datagrid>
            <DateField source="date" />
            <StarRatingField />
            <TextField source="comment" />
            <TextField source="status" />
            <EditButton />
          </Datagrid>
        </ReferenceManyField>
        <CreateRelatedReviewButton />
      </TabbedForm.Tab>
    </TabbedForm>
  </Edit>
);

Finally, we’ll write a creation form for the review. It will be a standard react-admin creation page—no need to set the form default values based on the location state, as <Create> does it automatically. We’ll just override the success callback to redirect to the related product page after submission:

// in src/reviews/ReviewCreate.tsx
import {
  SimpleForm,
  Create,
  ReferenceInput,
  TextInput,
  DateInput,
  AutocompleteInput,
  required,
  useNotify,
  useRedirect,
  getRecordFromLocation,
} from "react-admin";

const ReviewCreate = () => {
  const notify = useNotify();
  const redirect = useRedirect();
  const location = useLocation();

  const onSuccess = () => {
    // display a notification to confirm the creation
    notify("ra.notification.created");
    // get the initial values we set in the state earlier to know whether a product_id was provided
    const record = getRecordFromLocation(location);
    if (record && record.product_id) {
      // the record was created from the edit view of the product, redirect to it
      redirect(`/products/${record.product_id}/reviews`);
    } else {
      // redirect to the list of reviews
      redirect(`/reviews`);
    }
  };

  return (
    <Create mutationOptions={{ onSuccess }}>
      <SimpleForm>
        // ...
        <ReferenceInput source="product_id" reference="products">
          <AutocompleteInput
            optionText={productOptionRenderer}
            validate={required()}
          />
        </ReferenceInput>
        <DateInput
          source="date"
          defaultValue={new Date()}
          validate={required()}
        />
        <StarRatingInput source="rating" defaultValue={2} />
        <TextInput
          source="comment"
          multiline
          fullWidth
          resettable
          validate={required()}
        />
        // ...
      </SimpleForm>
    </Create>
  );
};

And that is it.

The previous code works fine, but the user experience is perfectible. Users have to leave the product page to go to the review creation page, only to come back to the product page on success. It would be better if the review creation form opened in a dialog, so that users would never need to leave the product page.

We can achieve that by replacing the <CreateButton> with a <CreateInDialogButton>: it opens a <Create> form in a <Dialog> without neither leaving the current view nor modifying the URL.

<CreateInDialogButton> expects a form as child. This means we don’t need to use the location state to set the form default values. We can just use the record from the current context, leveraging the <WithRecord> component:

// in src/products/ProductEdit.tsx
const ProductEdit = () => (
  <Edit>
    <TabbedForm>
      // ...
      <TabbedForm.Tab>
        <ReferenceManyField reference="reviews" target="product_id">
          <Datagrid>
            <DateField source="date" />
            <StarRatingField />
            <TextField source="comment" />
            <TextField source="status" />
            <EditButton />
          </Datagrid>
        </ReferenceManyField>
        <WithRecord
          render={(record) => (
            <CreateInDialogButton record={{ product_id: record.id }}>
              <SimpleForm>
                // ...
                <DateInput
                  source="date"
                  defaultValue={new Date()}
                  validate={required()}
                />
                <StarRatingInput source="rating" defaultValue={2} />
                <TextInput
                  source="comment"
                  multiline
                  fullWidth
                  resettable
                  validate={required()}
                />
              </SimpleForm>
            </CreateInDialogButton>
          )}
        />
      </TabbedForm.Tab>
    </TabbedForm>
  </Edit>
);

Since the dialog closes after a successful form submission, we don’t need to customize the redirection to the product edition view anymore. Also, as the creation form will never be displayed directly, we don’t even need to include a <ReferenceInput> for the product_id. This solution requires less code and provides a better user experience than the first one.

Note that <CreateInDialogButton> is only a good fit for small forms, that can fit in a dialog. Also, it’s an Enterprise Edition component.

Conclusion

We hope this recipe will speed up your react-admin development and improve the user experience of your apps.

You can test both approaches and compare the the code in the react-admin e-commerce demo:

Your registration is confirmed.

Keep up to date

Join our react-admin newsletter for regular updates. No spam ever.

Authors

Matthieu Chaffotte

Full-stack web developer at marmelab, Matthieu has 10 years of experience as a backend (Java) developer. Teacher, architect and happy father, he also understands the rules of baseball.

Comments