diff --git a/docs/Forms.md b/docs/Forms.md index cf224c23d2e..e65536ed77a 100644 --- a/docs/Forms.md +++ b/docs/Forms.md @@ -314,7 +314,7 @@ const OrderEdit = () => ( ); ``` -**Tip**: When used inside an `ArrayInput`, `` provides one additional property to its child function called `scopedFormData`. It's an object containing the current values of the *currently rendered item*. This allows you to create dependencies between inputs inside a ``, as in the following example: +**Tip**: When used inside an `ArrayInput`, `` provides two additional properties to its child function: `scopedFormData` and `index`. `scopedFormData` is an object containing the current values of the *currently rendered item*. `index` is the index of the current item in the array. This allows you to create dependencies between inputs inside a ``, as in the following example: ```tsx import { FormDataConsumer } from 'react-admin'; @@ -329,6 +329,7 @@ const PostEdit = () => ( {({ formData, // The whole form data scopedFormData, // The data for this item of the ArrayInput + index, // The index of the current item in the ArrayInput ...rest }) => scopedFormData && scopedFormData.name ? ( @@ -347,7 +348,9 @@ const PostEdit = () => ( ); ``` -**Tip:** TypeScript users will notice that `scopedFormData` is typed as an optional parameter. This is because the `` component can be used outside of an `` and in that case, this parameter will be `undefined`. If you are inside an ``, you can safely assume that this parameter will be defined. +**Tip:** TypeScript users will notice that `scopedFormData` and `index` are typed as optional parameters. This is because the `` component can be used outside of an `` and in that case, these parameters will be `undefined`. If you are inside an ``, you can safely assume that these parameters will be defined. + +**Tip:** The `index` parameter is useful when you need to know the position of the current item in the array, e.g. to display a row number or to apply conditional logic based on the item's position. ## Hiding Inputs Based On Other Inputs diff --git a/docs/Inputs.md b/docs/Inputs.md index f5b92675856..871a57e0939 100644 --- a/docs/Inputs.md +++ b/docs/Inputs.md @@ -673,7 +673,7 @@ const OrderEdit = () => ( ); ``` -**Tip**: When used inside an ``, `` provides one additional property to its child function called `scopedFormData`. It's an object containing the current values of the *currently rendered item*. This allows you to create dependencies between inputs inside a ``, as in the following example: +**Tip**: When used inside an ``, `` provides two additional properties to its child function: `scopedFormData` and `index`. `scopedFormData` is an object containing the current values of the *currently rendered item*. `index` is the index of the current item in the array. This allows you to create dependencies between inputs inside a ``, as in the following example: ```tsx import { FormDataConsumer } from 'react-admin'; @@ -688,6 +688,7 @@ const PostEdit = () => ( {({ formData, // The whole form data scopedFormData, // The data for this item of the ArrayInput + index, // The index of the current item in the ArrayInput ...rest }) => scopedFormData && scopedFormData.name ? ( @@ -706,7 +707,9 @@ const PostEdit = () => ( ); ``` -**Tip:** TypeScript users will notice that `scopedFormData` is typed as an optional parameter. This is because the `` component can be used outside of an `` and in that case, this parameter will be `undefined`. If you are inside an ``, you can safely assume that this parameter will be defined. +**Tip:** TypeScript users will notice that `scopedFormData` and `index` are typed as optional parameters. This is because the `` component can be used outside of an `` and in that case, these parameters will be `undefined`. If you are inside an ``, you can safely assume that these parameters will be defined. + +**Tip:** The `index` parameter is useful when you need to know the position of the current item in the array, e.g. to display a row number or to apply conditional logic based on the item's position. **Tip:** If you need to access the *effective* source of an input inside an ``, for example to change the value programmatically using `setValue`, you will need to leverage the [`useSourceContext` hook](./ArrayInput#changing-an-items-value-programmatically). diff --git a/docs/SimpleFormIterator.md b/docs/SimpleFormIterator.md index 44d3b0762b9..ba2b389e7ce 100644 --- a/docs/SimpleFormIterator.md +++ b/docs/SimpleFormIterator.md @@ -135,7 +135,7 @@ A list of Input elements, that will be rendered on each row. By default, `` renders one input per line, but they can be displayed inline with the `inline` prop. -`` also accepts `` as child. In this case, `` provides one additional property to its child function called `scopedFormData`. It's an object containing the current values of the *currently rendered item*. This allows you to create dependencies between inputs inside a ``, as in the following example: +`` also accepts `` as child. In this case, `` provides two additional properties to its child function: `scopedFormData` and `index`. `scopedFormData` is an object containing the current values of the *currently rendered item*. `index` is the index of the current item in the array. This allows you to create dependencies between inputs inside a ``, as in the following example: ```jsx import { FormDataConsumer } from 'react-admin'; @@ -150,6 +150,7 @@ const PostEdit = () => ( {({ formData, // The whole form data scopedFormData, // The data for this item of the ArrayInput + index, // The index of the current item in the ArrayInput }) => scopedFormData && scopedFormData.name ? ( ( ); ``` -**Tip:** TypeScript users will notice that `scopedFormData` is typed as an optional parameter. This is because the `` component can be used outside of an `` and in that case, this parameter will be `undefined`. If you are inside an ``, you can safely assume that this parameter will be defined. +**Tip:** TypeScript users will notice that `scopedFormData` and `index` are typed as optional parameters. This is because the `` component can be used outside of an `` and in that case, these parameters will be `undefined`. If you are inside an ``, you can safely assume that these parameters will be defined. + +**Tip:** The `index` parameter is useful when you need to know the position of the current item in the array, e.g. to display a row number or to apply conditional logic based on the item's position. **Note**: `` only accepts `Input` components as children. If you want to use some `Fields` instead, you have to use a ``, as follows: diff --git a/packages/ra-core/src/form/FormDataConsumer.spec.tsx b/packages/ra-core/src/form/FormDataConsumer.spec.tsx index 6498b370990..a45fc7acc88 100644 --- a/packages/ra-core/src/form/FormDataConsumer.spec.tsx +++ b/packages/ra-core/src/form/FormDataConsumer.spec.tsx @@ -140,4 +140,78 @@ describe('FormDataConsumerView', () => { }); }); }); + + it('calls its children with the index when inside an ArrayInput', async () => { + let globalIndex; + + render( + + + + + + + {({ index }) => { + globalIndex = index; + return null; + }} + + + + + + + ); + + expect(globalIndex).toEqual(undefined); + + fireEvent.click(screen.getByLabelText('ra.action.add')); + + expect(globalIndex).toEqual(0); + + fireEvent.click(screen.getByLabelText('ra.action.add')); + + expect(globalIndex).toEqual(1); + }); + + it('calls its children with the correct index when inside nested ArrayInputs', async () => { + let innerIndex: number | undefined; + + render( + + + + + + + + + {({ index }) => { + innerIndex = index; + return null; + }} + + + + + + + + + ); + + await waitFor(() => { + // The inner array's first item should have index 0, + // not the outer array's index (also 0 in this case) + expect(innerIndex).toEqual(0); + }); + }); }); diff --git a/packages/ra-core/src/form/FormDataConsumer.tsx b/packages/ra-core/src/form/FormDataConsumer.tsx index f0f4176ec1e..215d0da6616 100644 --- a/packages/ra-core/src/form/FormDataConsumer.tsx +++ b/packages/ra-core/src/form/FormDataConsumer.tsx @@ -77,7 +77,9 @@ export const FormDataConsumerView = < // If we have an index, we are in an iterator like component (such as the SimpleFormIterator) if (arraySource) { const scopedFormData = get(formData, arraySource); - result = children({ formData, scopedFormData }); + const matches = [...arraySource.matchAll(/\d+/g)]; + const index = Number(matches[matches.length - 1]?.[0]); + result = children({ formData, scopedFormData, index }); } else { result = children({ formData }); } @@ -98,6 +100,7 @@ export interface FormDataConsumerRenderParams< > { formData: TFieldValues; scopedFormData?: TScopedFieldValues; + index?: number; } export type FormDataConsumerRender<