-
-
Notifications
You must be signed in to change notification settings - Fork 5.4k
[Doc] Add <ReferenceInputBase>
and <ReferenceArrayInputBase>
documentation in headless doc site
#10965
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
[Doc] Add <ReferenceInputBase>
and <ReferenceArrayInputBase>
documentation in headless doc site
#10965
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
caf29ee
Add `<ReferenceInputBase>` documentation
djhi 804a84d
Add `<ReferenceArrayInputBase>` documentation
djhi 675a960
Update sidebar menu
djhi 15b6447
Improve imports
djhi 4db35b6
Improve documentation
djhi b2814ce
Apply review suggestions
djhi f3ad55e
Fix navigation
djhi 18462e3
Merge branch 'master' into ra-core-reference-doc
djhi 0a07b0b
review
slax57 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
330 changes: 330 additions & 0 deletions
330
docs_headless/src/content/docs/ReferenceArrayInputBase.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,330 @@ | ||
--- | ||
title: "<ReferenceArrayInputBase>" | ||
--- | ||
|
||
`<ReferenceArrayInputBase>` is useful for editing an array of reference values, i.e. to let users choose a list of values (usually foreign keys) from another REST endpoint. | ||
`<ReferenceArrayInputBase>` is a headless component, handling only the logic. This allows to use any UI library for the render. | ||
|
||
## Usage | ||
|
||
For instance, a post record has a `tag_ids` field, which is an array of foreign keys to tags record. | ||
|
||
``` | ||
┌──────────────┐ ┌────────────┐ | ||
│ post │ │ tags │ | ||
│--------------│ │------------│ | ||
│ id │ ┌───│ id │ | ||
│ title │ │ │ name │ | ||
│ body │ │ └────────────┘ | ||
│ tag_ids │───┘ | ||
└──────────────┘ | ||
``` | ||
|
||
To make the `tag_ids` for a `post` editable, use the following: | ||
|
||
```jsx | ||
import { EditBase, ReferenceArrayInputBase, Form, useChoicesContext, useInput } from 'ra-core'; | ||
import { TextInput } from 'my-react-admin-ui'; | ||
|
||
const PostEdit = () => ( | ||
<EditBase> | ||
<Form> | ||
<TextInput source="title" /> | ||
<ReferenceArrayInputBase source="tag_ids" reference="tags"> | ||
<TagSelector /> | ||
</ReferenceArrayInputBase> | ||
<button type="submit">Save</button> | ||
</Form> | ||
</EditBase> | ||
); | ||
|
||
const TagSelector = () => { | ||
const { allChoices, isLoading, error, source } = useChoicesContext(); | ||
const { field, id } = useInput({ source }); | ||
|
||
if (isLoading) return <div>Loading...</div>; | ||
if (error) return <div>Error: {error.message}</div>; | ||
|
||
const handleCheckboxChange = (choiceId) => { | ||
const currentValue = field.value || []; | ||
const newValue = currentValue.includes(choiceId) | ||
? currentValue.filter(id => id !== choiceId) | ||
: [...currentValue, choiceId]; | ||
field.onChange(newValue); | ||
}; | ||
|
||
return ( | ||
<fieldset> | ||
<legend>Select tags</legend> | ||
{allChoices.map(choice => ( | ||
<label key={choice.id} style={{ display: 'block' }}> | ||
<input | ||
type="checkbox" | ||
name={field.name} | ||
checked={(field.value || []).includes(choice.id)} | ||
onChange={() => handleCheckboxChange(choice.id)} | ||
onBlur={field.onBlur} | ||
/> | ||
{choice.name} | ||
</label> | ||
))} | ||
</fieldset> | ||
); | ||
}; | ||
``` | ||
|
||
`<ReferenceArrayInputBase>` requires a `source` and a `reference` prop. | ||
|
||
`<ReferenceArrayInputBase>` uses the array of foreign keys to fetch the related records. It also grabs the list of possible choices for the field. For instance, if the `PostEdit` component above is used to edit the following post: | ||
|
||
```js | ||
{ | ||
id: 1234, | ||
title: "Lorem Ipsum", | ||
body: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", | ||
tag_ids: [1, 23, 4] | ||
} | ||
``` | ||
|
||
Then `<ReferenceArrayInputBase>` will issue the following queries: | ||
|
||
```js | ||
dataProvider.getMany('tags', { ids: [1, 23, 4] }); | ||
dataProvider.getList('tags', { | ||
filter: {}, | ||
sort: { field: 'id', order: 'DESC' }, | ||
pagination: { page: 1, perPage: 25 } | ||
}); | ||
``` | ||
|
||
`<ReferenceArrayInputBase>` handles the data fetching and provides the choices through a [`ChoicesContext`](./useChoicesContext.md). It's up to the child components to render the selection interface. | ||
|
||
You can tweak how `<ReferenceArrayInputBase>` fetches the possible values using the `page`, `perPage`, `sort`, and `filter` props. | ||
|
||
## Props | ||
|
||
| Prop | Required | Type | Default | Description | | ||
|--------------------|----------|---------------------------------------------|------------------------------------|---------------------------------------------------------------------------------------------------------------------| | ||
| `source` | Required | `string` | - | Name of the entity property to use for the input value | | ||
| `reference` | Required | `string` | '' | Name of the reference resource, e.g. 'tags'. | | ||
| `children` | Required | `ReactNode` | - | The actual selection component | | ||
| `render` | Optional | `(context) => ReactNode` | - | Function that takes the choices context and renders the selection interface | | ||
| `enableGetChoices` | Optional | `({q: string}) => boolean` | `() => true` | Function taking the `filterValues` and returning a boolean to enable the `getList` call. | | ||
| `filter` | Optional | `Object` | `{}` | Permanent filters to use for getting the suggestion list | | ||
| `offline` | Optional | `ReactNode` | - | What to render when there is no network connectivity when loading the record | | ||
| `page` | Optional | `number` | 1 | The current page number | | ||
| `perPage` | Optional | `number` | 25 | Number of suggestions to show | | ||
| `queryOptions` | Optional | [`UseQueryOptions`](https://tanstack.com/query/v5/docs/react/reference/useQuery) | `{}` | `react-query` client options | | ||
| `sort` | Optional | `{ field: String, order: 'ASC' or 'DESC' }` | `{ field: 'id', order: 'DESC' }` | How to order the list of suggestions | | ||
|
||
## `children` | ||
|
||
You can pass any component of your own as child, to render the selection interface as you wish. | ||
You can access the choices context using the `useChoicesContext` hook. | ||
|
||
```tsx | ||
import { ReferenceArrayInputBase, useChoicesContext, useInput } from 'ra-core'; | ||
|
||
export const CustomArraySelector = () => { | ||
const { allChoices, isLoading, error, source } = useChoicesContext(); | ||
const { field, id } = useInput({ source }); | ||
|
||
if (isLoading) { | ||
return <div>Loading...</div>; | ||
} | ||
|
||
if (error) { | ||
return <div className="error">{error.toString()}</div>; | ||
} | ||
|
||
const handleCheckboxChange = (choiceId) => { | ||
const currentValue = field.value || []; | ||
const newValue = currentValue.includes(choiceId) | ||
? currentValue.filter(id => id !== choiceId) | ||
: [...currentValue, choiceId]; | ||
field.onChange(newValue); | ||
}; | ||
|
||
return ( | ||
<fieldset> | ||
<legend>Select multiple tags</legend> | ||
{allChoices.map(choice => ( | ||
<label key={choice.id} style={{ display: 'block' }}> | ||
<input | ||
type="checkbox" | ||
name={field.name} | ||
slax57 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
checked={(field.value || []).includes(choice.id)} | ||
onChange={() => handleCheckboxChange(choice.id)} | ||
onBlur={field.onBlur} | ||
/> | ||
{choice.name} | ||
</label> | ||
))} | ||
</fieldset> | ||
); | ||
}; | ||
|
||
export const MyReferenceArrayInput = () => ( | ||
<ReferenceArrayInputBase source="tag_ids" reference="tags"> | ||
<CustomArraySelector /> | ||
</ReferenceArrayInputBase> | ||
); | ||
``` | ||
|
||
## `render` | ||
|
||
Alternatively, you can pass a `render` function prop instead of children. This function will receive the `ChoicesContext` as argument. | ||
|
||
```jsx | ||
export const MyReferenceArrayInput = () => ( | ||
<ReferenceArrayInputBase | ||
source="tag_ids" | ||
reference="tags" | ||
render={({ choices, isLoading, error }) => { | ||
if (isLoading) { | ||
return <div>Loading...</div>; | ||
} | ||
|
||
if (error) { | ||
return ( | ||
<div className="error"> | ||
{error.message} | ||
</div> | ||
); | ||
} | ||
|
||
return ( | ||
<select multiple> | ||
{choices.map(choice => ( | ||
<option key={choice.id} value={choice.id}> | ||
{choice.name} | ||
</option> | ||
))} | ||
</select> | ||
); | ||
}} | ||
/> | ||
); | ||
``` | ||
|
||
The `render` function prop will take priority on `children` props if both are set. | ||
|
||
## `enableGetChoices` | ||
|
||
You can make the `getList()` call lazy by using the `enableGetChoices` prop. This prop should be a function that receives the `filterValues` as parameter and return a boolean. This can be useful when using a search input on a resource with a lot of data. The following example only starts fetching the options when the query has at least 2 characters: | ||
|
||
```jsx | ||
<ReferenceArrayInputBase | ||
source="tag_ids" | ||
reference="tags" | ||
enableGetChoices={({ q }) => q && q.length >= 2} | ||
slax57 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/> | ||
``` | ||
|
||
## `filter` | ||
|
||
You can filter the query used to populate the possible values. Use the `filter` prop for that. | ||
|
||
```jsx | ||
<ReferenceArrayInputBase source="tag_ids" reference="tags" filter={{ is_published: true }} /> | ||
``` | ||
|
||
## `offline` | ||
|
||
`<ReferenceArrayInputBase>` can display a custom message when the referenced record is missing because there is no network connectivity, thanks to the `offline` prop. | ||
|
||
```jsx | ||
<ReferenceArrayInputBase source="tag_ids" reference="tags" offline="No network, could not fetch data" /> | ||
``` | ||
|
||
`<ReferenceArrayInputBase>` renders the `offline` element when: | ||
|
||
- the referenced record is missing (no record in the `tags` table with the right `tag_ids`), and | ||
- there is no network connectivity | ||
|
||
You can pass either a React element or a string to the `offline` prop: | ||
|
||
```jsx | ||
<ReferenceArrayInputBase source="tag_ids" reference="tags" offline={<span>No network, could not fetch data</span>} /> | ||
<ReferenceArrayInputBase source="tag_ids" reference="tags" offline="No network, could not fetch data" /> | ||
``` | ||
|
||
## `perPage` | ||
|
||
By default, `<ReferenceArrayInputBase>` fetches only the first 25 values. You can extend this limit by setting the `perPage` prop. | ||
|
||
```jsx | ||
<ReferenceArrayInputBase source="tag_ids" reference="tags" perPage={100} /> | ||
``` | ||
|
||
## `queryOptions` | ||
|
||
Use the `queryOptions` prop to pass options to the `dataProvider.getList()` query that fetches the possible choices. | ||
|
||
For instance, to pass [a custom `meta`](./Actions.md#meta-parameter): | ||
|
||
```jsx | ||
<ReferenceArrayInputBase | ||
source="tag_ids" | ||
reference="tags" | ||
queryOptions={{ meta: { foo: 'bar' } }} | ||
/> | ||
``` | ||
|
||
## `reference` | ||
|
||
The name of the reference resource. For instance, in a post form, if you want to edit the post tags, the reference should be "tags". | ||
|
||
```jsx | ||
<ReferenceArrayInputBase source="tag_ids" reference="tags" /> | ||
``` | ||
|
||
`<ReferenceArrayInputBase>` will use the reference resource [`recordRepresentation`](./Resource.md#recordrepresentation) to display the selected record and the list of possible records. So for instance, if the `tags` resource is defined as follows: | ||
|
||
```jsx | ||
<Resource name="tags" recordRepresentation="name" /> | ||
``` | ||
|
||
Then `<ReferenceArrayInputBase>` will display the tag name in the choices list. | ||
|
||
## `sort` | ||
|
||
By default, `<ReferenceArrayInputBase>` orders the possible values by `id` desc. | ||
|
||
You can change this order by setting the `sort` prop (an object with `field` and `order` properties). | ||
|
||
```jsx | ||
<ReferenceArrayInputBase | ||
source="tag_ids" | ||
reference="tags" | ||
sort={{ field: 'name', order: 'ASC' }} | ||
/> | ||
``` | ||
|
||
## `source` | ||
|
||
The name of the property in the record that contains the array of identifiers of the selected record. | ||
|
||
For instance, if a post contains a reference to tags via a `tag_ids` property: | ||
|
||
```js | ||
{ | ||
id: 456, | ||
title: "Hello, world!", | ||
tag_ids: [123, 456] | ||
} | ||
``` | ||
|
||
Then to display a selector for the post tags, you should call `<ReferenceArrayInputBase>` as follows: | ||
|
||
```jsx | ||
<ReferenceArrayInputBase source="tag_ids" reference="tags" /> | ||
``` | ||
|
||
## Performance | ||
|
||
Why does `<ReferenceArrayInputBase>` use the `dataProvider.getMany()` method with multiple values `[id1, id2, ...]` instead of multiple `dataProvider.getOne()` calls to fetch the records for the current values? | ||
|
||
Because when there may be many `<ReferenceArrayInputBase>` for the same resource in a form (for instance when inside an `<ArrayInput>`), react-admin *aggregates* the calls to `dataProvider.getMany()` into a single one with `[id1, id2, id3, ...]`. | ||
|
||
This speeds up the UI and avoids hitting the API too much. |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.