-
-
Notifications
You must be signed in to change notification settings - Fork 5.4k
Introduce <ArrayInputBase>
, <SimpleFomIteratorBase>
and <SimpleFormIteratorItemBase>
#10955
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
+2,271
−436
Merged
Changes from all commits
Commits
Show all changes
29 commits
Select commit
Hold shift + click to select a range
07fffeb
Introduce `<ArrayInputBase>`, `<SimpleFomIteratorBase>` and `<SimpleF…
djhi 2bd511d
Fix build
djhi fb34fc0
Improve API
djhi ecaa065
Add some comments [no ci]
djhi 51b93be
Improve API
djhi ff63ccb
Introduce minimal test ui
djhi ad72de4
Add ArrayInputBase story
djhi afacca7
Add headless documentation
djhi 882aa5e
Fix test-ui export
djhi 94f18d4
Rename `onAddItem` to `getItemDefaults`
djhi 62fd603
Fix test-ui exports
djhi b305858
Cleanup test-ui `<TextInput>`
djhi 76cfb03
Improve `<SimpleFormIteratorItem>` JSDoc
djhi 2187d9c
Use `<ArrayInputBase>` in test-ui
djhi 1b68259
Avoid breaking change in `<SimpleFormIteratorItem>`
djhi 044d631
FIx `<SimpleFormIteratorClearButton>` className prop
djhi 9bc7fea
Merge branch 'next' into array-input-base
djhi 744bd66
Improve documentation
djhi c2b8f79
Merge branch 'next' into array-input-base
djhi 1ed9b9a
Improve documentation
djhi 69bbd27
Ensure users can override `getItemDefaults` in `<SimpleFormIterator>`
djhi 18e8882
Add a basic story for `SimpleFormIteratorBase`
djhi 78da761
Improve documentation
djhi 6be804c
Remove unnecessary props
djhi 37232dd
Improve SimpleFormIteratorBase documentation
djhi a6cfd80
Fix ArrayInputBase documentation
djhi 4f55868
Fix breaking change in SimpleFormIterator for clear button
djhi 9476d68
FIx documentation and JSDoc
djhi 84808da
[no ci] remove dead link
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
--- | ||
layout: default | ||
title: "<ArrayInputBase>" | ||
--- | ||
slax57 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
`<ArrayInputBase>` allows editing of embedded arrays, like the `items` field in the following `order` record: | ||
|
||
```json | ||
{ | ||
"id": 1, | ||
"date": "2022-08-30", | ||
"customer": "John Doe", | ||
"items": [ | ||
{ | ||
"name": "Office Jeans", | ||
"price": 45.99, | ||
"quantity": 1, | ||
}, | ||
{ | ||
"name": "Black Elegance Jeans", | ||
"price": 69.99, | ||
"quantity": 2, | ||
}, | ||
{ | ||
"name": "Slim Fit Jeans", | ||
"price": 55.99, | ||
"quantity": 1, | ||
}, | ||
], | ||
} | ||
``` | ||
|
||
## Usage | ||
|
||
`<ArrayInputBase>` expects a single child, which must be a *form iterator* component. A form iterator is a component rendering a field array (the object returned by react-hook-form's [`useFieldArray`](https://react-hook-form.com/docs/usefieldarray)). You can build such component using [the `<SimpleFormIteratorBase>`](./SimpleFormIteratorBase.md). | ||
|
||
```tsx | ||
import { ArrayInputBase, EditBase, Form } from 'ra-core'; | ||
import { MyFormIterator } from './MyFormIterator'; | ||
slax57 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
import { DateInput } from './DateInput'; | ||
import { NumberInput } from './NumberInput'; | ||
import { TextInput } from './TextInput'; | ||
|
||
export const OrderEdit = () => ( | ||
<EditBase> | ||
<Form> | ||
<DateInput source="date" /> | ||
<div> | ||
<div>Items:</div> | ||
<ArrayInputBase source="items"> | ||
<MyFormIterator> | ||
<TextInput source="name" /> | ||
<NumberInput source="price" /> | ||
<NumberInput source="quantity" /> | ||
</MyFormIterator> | ||
</ArrayInputBase> | ||
</div> | ||
<button type="submit">Save</button> | ||
</Form> | ||
</EditBase> | ||
) | ||
``` | ||
|
||
**Note**: Setting [`shouldUnregister`](https://react-hook-form.com/docs/useform#shouldUnregister) on a form should be avoided when using `<ArrayInputBase>` (which internally uses `useFieldArray`) as the unregister function gets called after input unmount/remount and reorder. This limitation is mentioned in the react-hook-form [documentation](https://react-hook-form.com/docs/usecontroller#props). If you are in such a situation, you can use the [`transform`](./EditBase.md#transform) prop to manually clean the submitted values. | ||
|
||
## Props | ||
|
||
| Prop | Required | Type | Default | Description | | ||
|-----------------| -------- |---------------------------| ------- |---------------------------------------------------------------------------------------------------------------------------------------------------------------------| | ||
| `source` | Required | `string` | - | Name of the entity property to use for the input value | | ||
| `defaultValue` | Optional | `any` | - | Default value of the input. | | ||
| `validate` | Optional | `Function` | `array` | - | Validation rules for the current property. See the [Validation Documentation](./Validation.md#per-input-validation-built-in-field-validators) for details. | | ||
|
||
## Global validation | ||
|
||
If you are using an `<ArrayInputBase>` inside a form with global validation, you need to shape the errors object returned by the `validate` function like an array too. | ||
|
||
For instance, to display the following errors: | ||
|
||
 | ||
|
||
You need to return an errors object shaped like this: | ||
|
||
```js | ||
{ | ||
authors: [ | ||
{}, | ||
{ | ||
name: 'A name is required', | ||
role: 'ra.validation.required' // translation keys are supported too | ||
}, | ||
], | ||
} | ||
``` | ||
|
||
**Tip:** You can find a sample `validate` function that handles arrays in the [Form Validation documentation](./Validation.md#global-validation). | ||
|
||
## Disabling The Input | ||
|
||
`<ArrayInputBase>` does not support the `disabled` and `readOnly` props. | ||
|
||
If you need to disable the input, make sure the children are either `disabled` and `readOnly`: | ||
|
||
```jsx | ||
import { ArrayInputBase, EditBase, Form } from 'ra-core'; | ||
import { MyFormIterator } from './MyFormIterator'; | ||
import { DateInput } from './DateInput'; | ||
import { NumberInput } from './NumberInput'; | ||
import { TextInput } from './TextInput'; | ||
|
||
const OrderEdit = () => ( | ||
<EditBase> | ||
<Form> | ||
<TextInput source="customer" /> | ||
<DateInput source="date" /> | ||
<div> | ||
<div>Items:</div> | ||
<ArrayInputBase source="items"> | ||
<MyFormIterator inline disabled> | ||
<TextInput source="name" readOnly/> | ||
<NumberInput source="price" readOnly /> | ||
<NumberInput source="quantity" readOnly /> | ||
</MyFormIterator> | ||
</ArrayInputBase> | ||
</div> | ||
<button type="submit">Save</button> | ||
</Form> | ||
</EditBase> | ||
); | ||
``` | ||
|
||
## Changing An Item's Value Programmatically | ||
|
||
You can leverage `react-hook-form`'s [`setValue`](https://react-hook-form.com/docs/useform/setvalue) method to change an item's value programmatically. | ||
|
||
However you need to know the `name` under which the input was registered in the form, and this name is dynamically generated depending on the index of the item in the array. | ||
|
||
To get the name of the input for a given index, you can leverage the `SourceContext` created by react-admin, which can be accessed using the `useSourceContext` hook. | ||
|
||
This context provides a `getSource` function that returns the effective `source` for an input in the current context, which you can use as input name for `setValue`. | ||
|
||
Here is an example where we leverage `getSource` and `setValue` to change the role of an user to 'admin' when the 'Make Admin' button is clicked: | ||
|
||
```tsx | ||
import { ArrayInputBase, useSourceContext } from 'ra-core'; | ||
import { useFormContext } from 'react-hook-form'; | ||
import { MyFormIterator } from './MyFormIterator'; | ||
|
||
const MakeAdminButton = () => { | ||
const sourceContext = useSourceContext(); | ||
const { setValue } = useFormContext(); | ||
|
||
const onClick = () => { | ||
// sourceContext.getSource('role') will for instance return | ||
// 'users.0.role' | ||
setValue(sourceContext.getSource('role'), 'admin'); | ||
}; | ||
|
||
return ( | ||
<button onClick={onClick}> | ||
Make admin | ||
</button> | ||
); | ||
}; | ||
|
||
const UserArray = () => ( | ||
<ArrayInputBase source="users"> | ||
<MyFormIterator inline> | ||
<TextInput source="name" helperText={false} /> | ||
<TextInput source="role" helperText={false} /> | ||
<MakeAdminButton /> | ||
</MyFormIterator> | ||
</ArrayInputBase> | ||
); | ||
``` | ||
|
||
**Tip:** If you only need the item's index, you can leverage the `useSimpleFormIteratorItem` hook instead. |
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,70 @@ | ||
--- | ||
layout: default | ||
title: "<SimpleFormIteratorBase>" | ||
--- | ||
|
||
`<SimpleFormIteratorBase>` helps building a component that lets users edit, add, remove and reorder sub-records. It is designed to be used as a child of [`<ArrayInputBase>`](./ArrayInputBase.md) or [`<ReferenceManyInputBase>`](https://react-admin-ee.marmelab.com/documentation/ra-core-ee#referencemanyinputbase). You can also use it within an `ArrayInputContext` containing a *field array*, i.e. the value returned by [react-hook-form's `useFieldArray` hook](https://react-hook-form.com/docs/usefieldarray). | ||
|
||
## Usage | ||
|
||
Here's how one could implement a minimal `SimpleFormIterator` using `<SimpleFormIteratorBase>`: | ||
|
||
```tsx | ||
import { | ||
SimpleFormIteratorBase, | ||
SimpleFormIteratorItemBase, | ||
useArrayInput, | ||
useFieldValue, | ||
useSimpleFormIterator, | ||
useSimpleFormIteratorItem, | ||
useWrappedSource, | ||
type SimpleFormIteratorBaseProps | ||
} from 'ra-core'; | ||
|
||
export const SimpleFormIterator = ({ children, ...props }: SimpleFormIteratorBaseProps) => { | ||
slax57 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const { fields } = useArrayInput(props); | ||
// Get the parent source by passing an empty string as source | ||
const source = useWrappedSource(''); | ||
const records = useFieldValue({ source }); | ||
|
||
return ( | ||
<SimpleFormIteratorBase {...props}> | ||
<ul> | ||
{fields.map((member, index) => ( | ||
<SimpleFormIteratorItemBase | ||
key={member.id} | ||
index={index} | ||
record={record} | ||
> | ||
<li> | ||
{children} | ||
<RemoveItemButton /> | ||
</li> | ||
</SimpleFormIteratorItemBase> | ||
))} | ||
</ul> | ||
<AddItemButton /> | ||
</SimpleFormIteratorBase> | ||
) | ||
} | ||
|
||
const RemoveItemButton = () => { | ||
const { remove } = useSimpleFormIteratorItem(); | ||
return ( | ||
<button type="button" onClick={() => remove()}>Remove</button> | ||
) | ||
} | ||
|
||
const AddItemButton = () => { | ||
const { add } = useSimpleFormIterator(); | ||
return ( | ||
<button type="button" onClick={() => add()}>Add</button> | ||
) | ||
} | ||
``` | ||
|
||
## Props | ||
|
||
| Prop | Required | Type | Default | Description | | ||
|-------------------|----------|----------------|-----------------------|-----------------------------------------------| | ||
| `children` | Optional | `ReactElement` | - | List of inputs to display for each array item | |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
73 changes: 73 additions & 0 deletions
73
packages/ra-core/src/controller/input/ArrayInputBase.stories.tsx
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,73 @@ | ||
import * as React from 'react'; | ||
import fakeRestDataProvider from 'ra-data-fakerest'; | ||
import { TestMemoryRouter } from '../../routing'; | ||
import { EditBase } from '../edit'; | ||
import { | ||
Admin, | ||
DataTable, | ||
TextInput, | ||
SimpleFormIterator, | ||
SimpleForm, | ||
} from '../../test-ui'; | ||
import { ListBase } from '../list'; | ||
import { Resource } from '../../core'; | ||
import { ArrayInputBase } from './ArrayInputBase'; | ||
|
||
export default { title: 'ra-core/controller/input/ArrayInputBase' }; | ||
|
||
export const Basic = () => ( | ||
<TestMemoryRouter initialEntries={['/posts/1']}> | ||
<Admin | ||
dataProvider={fakeRestDataProvider({ | ||
posts: [ | ||
{ | ||
id: 1, | ||
title: 'Post 1', | ||
tags: [ | ||
{ name: 'Tag 1', color: 'red' }, | ||
{ name: 'Tag 2', color: 'blue' }, | ||
], | ||
}, | ||
{ id: 2, title: 'Post 2' }, | ||
], | ||
})} | ||
> | ||
<Resource | ||
name="posts" | ||
list={ | ||
<ListBase> | ||
<DataTable> | ||
<DataTable.Col source="title" /> | ||
<DataTable.Col | ||
label="Tags" | ||
render={record => | ||
record.tags | ||
? record.tags | ||
.map(tag => tag.name) | ||
.join(', ') | ||
: '' | ||
} | ||
/> | ||
</DataTable> | ||
</ListBase> | ||
} | ||
edit={ | ||
<EditBase> | ||
<SimpleForm> | ||
<TextInput source="title" /> | ||
<div> | ||
<div>Tags:</div> | ||
<ArrayInputBase source="tags"> | ||
<SimpleFormIterator> | ||
<TextInput source="name" /> | ||
<TextInput source="color" /> | ||
</SimpleFormIterator> | ||
</ArrayInputBase> | ||
</div> | ||
</SimpleForm> | ||
</EditBase> | ||
} | ||
/> | ||
</Admin> | ||
</TestMemoryRouter> | ||
); |
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.