API Platform Admin handles one-to-one
, many-to-one
and one-to-many
relations automatically.
However, in some cases, dependeing on whether or not you chose to embed the relation in the serialized data, you may need to customize the way the relation is displayed and/or can be edited.
You can configure your API to embed the related data in the serialized response.
// Without Embedded Book Data
{
"@id": "/reviews/15",
id: 15,
rating: 5,
body: "A must-read for any software developer. Martin's insights are invaluable.",
author: "Alice Smith",
book: "/books/7"
}
// With Embedded Book Data
{
"@id": "/reviews/15",
id: 15,
rating: 5,
body: "A must-read for any software developer. Martin's insights are invaluable.",
author: "Alice Smith",
book: {
"@id": "/books/7",
id: 7,
title: "Clean Code",
author: "Robert C. Martin",
}
}
If you do so, by default the admin will render the full object as text field and text input, which is not very user-friendly.
There are two ways you can handle this situation:
useEmbedded
parameterReact Admin fields allow to use the dot notation (e.g. book.title
) to target a field from an embedded relation.
import {
HydraAdmin,
FieldGuesser,
ListGuesser,
ResourceGuesser,
} from '@api-platform/admin';
import { TextField } from 'react-admin';
const ReviewList = () => (
<ListGuesser>
<FieldGuesser source="rating" />
<FieldGuesser source="body" />
<FieldGuesser source="author" />
{/* Use react-admin components directly when you want complex fields. */}
<TextField label="Book" source="book.title" />
</ListGuesser>
);
export const App = () => (
<HydraAdmin entrypoint={...} >
<ResourceGuesser name="reviews" list={ReviewList} />
</HydraAdmin>
);
Allowing to edit the relation, on the other hand, is a little trickier, as it requires transforming the record to replace the nested object by its IRI.
Fortunately, this can be done by leveraging the transform
prop of the <EditGuesser>
component.
We can edit the relation by leveraging either <ReferenceInput>
for a to-one
relation or <ReferenceArrayInput>
for a to-many
relation.
import {
HydraAdmin,
InputGuesser,
EditGuesser,
ResourceGuesser,
} from '@api-platform/admin';
import { ReferenceInput, AutocompleteInput } from 'react-admin';
const reviewEditTransform = (values) => ({
...values,
book: values.book['@id'],
});
const ReviewEdit = () => (
<EditGuesser transform={reviewEditTransform}>
<InputGuesser source="rating" />
<InputGuesser source="body" />
<InputGuesser source="author" />
<ReferenceInput source="book.@id" reference="books">
<AutocompleteInput
label="Book"
filterToQuery={(searchText) => ({ title: searchText })}
/>
</ReferenceInput>
</EditGuesser>
);
export const App = () => (
<HydraAdmin entrypoint={...} >
<ResourceGuesser name="reviews" edit={ReviewEdit} />
</HydraAdmin>
);
This offers a nice and convenient way to edit the relation.
Tip: We also had to customize <ReferenceInput>
’s child <AutocompleteInput>
component to override its label
and filterToQuery
props. You can learn more about why that’s necessary in the Using an AutoComplete Input for Relations section.
You can also ask the admin to return the embedded resources’ IRI instead of the full record, by setting the useEmbedded
parameter of the Hydra data provider to false
.
// admin/src/App.jsx
import { HydraAdmin, dataProvider } from '@api-platform/admin';
const entrypoint = process.env.ENTRYPOINT;
export const App = () => (
<HydraAdmin
entrypoint={entrypoint}
dataProvider={dataProvider({
entrypoint,
useEmbedded: false,
})}
/>
);
This tells the dataProvider to return only the IRI in the record, discarding the embedded data.
// With useEmbedded=true (default)
const record = {
"@id": "/reviews/15",
id: 15,
rating: 5,
body: "A must-read for any software developer. Martin's insights are invaluable.",
author: "Alice Smith",
book: {
"@id": "/books/7",
id: 7,
title: "Clean Code",
author: "Robert C. Martin",
}
}
// With useEmbedded=false
const record = {
"@id": "/reviews/15",
id: 15,
rating: 5,
body: "A must-read for any software developer. Martin's insights are invaluable.",
author: "Alice Smith",
book: "/books/7"
}
This way, the related record’s IRI is returned and can be displayed.
We can improve the UI further by leveraging React Admin’s <ReferenceField>
component:
import {
HydraAdmin,
FieldGuesser,
ListGuesser,
ResourceGuesser,
} from '@api-platform/admin';
import { ReferenceField, TextField } from 'react-admin';
const ReviewList = () => (
<ListGuesser>
<FieldGuesser source="rating" />
<FieldGuesser source="body" />
<FieldGuesser source="author" />
<ReferenceField source="book" reference="books">
<TextField source="title" />
</ReferenceField>
</ListGuesser>
);
export const App = () => (
<HydraAdmin entrypoint={...} >
<ResourceGuesser name="reviews" list={ReviewList} />
</HydraAdmin>
);
This allows to display the title of the related book instead of its IRI.
Lastly, this also allows to easily edit the relation by leveraging either <ReferenceInput>
for a to-one
relation or <ReferenceArrayInput>
for a to-many
relation.
import {
HydraAdmin,
InputGuesser,
EditGuesser,
ResourceGuesser,
} from '@api-platform/admin';
import { ReferenceInput, AutocompleteInput } from 'react-admin';
const ReviewEdit = () => (
<EditGuesser>
<InputGuesser source="rating" />
<InputGuesser source="body" />
<InputGuesser source="author" />
<ReferenceInput source="book" reference="books">
<AutocompleteInput
filterToQuery={(searchText) => ({ title: searchText })}
/>
</ReferenceInput>
</EditGuesser>
);
export const App = () => (
<HydraAdmin entrypoint={...} >
<ResourceGuesser name="reviews" edit={ReviewEdit} />
</HydraAdmin>
);
This offers a nice and convenient way to edit the relation.
Tip: We also had to customize <ReferenceInput>
’s child <AutocompleteInput>
component to override its filterToQuery
props. You can learn more about why that’s necessary in the Using an AutoComplete Input for Relations section.
By default, <InputGuesser>
will render a <SelectInput>
when it detects a relation.
We can improve the UX further by rendering an <AutocompleteInput>
instead.
<AutocompleteInput>
allows to search for a related record by typing its name in an input field. This is much more convenient when there are many records to choose from.
Let’s consider an API exposing Review
and Book
resources linked by a many-to-one
relation (through the book
property).
This API uses the following PHP code:
<?php
// api/src/Entity/Review.php
namespace App\Entity;
use ApiPlatform\Metadata\ApiResource;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
#[ApiResource]
class Review
{
#[ORM\Id, ORM\Column, ORM\GeneratedValue]
public ?int $id = null;
#[ORM\ManyToOne]
public Book $book;
}
<?php
// api/src/Entity/Book.php
namespace App\Entity;
use ApiPlatform\Metadata\ApiFilter;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
#[ApiResource]
class Book
{
#[ORM\Id, ORM\Column, ORM\GeneratedValue]
public ?int $id = null;
#[ORM\Column]
#[ApiFilter(SearchFilter::class, strategy: 'ipartial')]
public string $title;
#[ORM\OneToMany(targetEntity: Review::class, mappedBy: 'book')]
public $reviews;
public function __construct()
{
$this->reviews = new ArrayCollection();
}
}
Notice the “partial search” filter on the title
property of the Book
resource class.
Now, let’s configure API Platform Admin to enable autocompletion for the book selector. We will leverage the <ReferenceInput>
and <AutocompleteInput>
components from React Admin:
import {
HydraAdmin,
ResourceGuesser,
CreateGuesser,
EditGuesser,
InputGuesser,
} from '@api-platform/admin';
import { ReferenceInput, AutocompleteInput } from 'react-admin';
const ReviewsEdit = () => (
<EditGuesser>
<InputGuesser source="author" />
<ReferenceInput source="book" reference="books">
<AutocompleteInput
filterToQuery={(searchText) => ({ title: searchText })}
optionText="title"
/>
</ReferenceInput>
<InputGuesser source="rating" />
<InputGuesser source="body" />
<InputGuesser source="publicationDate" />
</EditGuesser>
);
export const App = () => (
<HydraAdmin entrypoint={...} >
<ResourceGuesser name="reviews" edit={ReviewsEdit} />
</HydraAdmin>
);
The important things to note are:
filterToQuery
prop, which allows to search for books by title (leveraging the “partial search” filter mentioned above)optionText
prop, which tells the <AutocompleteInput>
component to render books using their title
propertyYou can now search for books by title in the book selector of the review form.
Thanks to the Schema.org support, you can easily display the name of a related resource instead of its IRI.
Follow the Displaying Related Resource’s Name Instead of its IRI section of the Schema.org documentation to implement this feature.
React Admin can handle many types of relations, even many-to-many
. You can learn more about them in the Fields For Relationships documentation.
You can also read the Handling Relationships in React Admin post from the React Admin blog for concrete examples and source code.
You can also help us improve the documentation of this page.
Made with love by
Les-Tilleuls.coop can help you design and develop your APIs and web projects, and train your teams in API Platform, Symfony, Next.js, Kubernetes and a wide range of other technologies.
Learn more