diff --git a/packages/ra-core/src/form/FormDataConsumer.spec.tsx b/packages/ra-core/src/form/FormDataConsumer.spec.tsx index 991a45ee3b9..f8a9a14d57c 100644 --- a/packages/ra-core/src/form/FormDataConsumer.spec.tsx +++ b/packages/ra-core/src/form/FormDataConsumer.spec.tsx @@ -1,7 +1,17 @@ import * as React from 'react'; -import { render } from '@testing-library/react'; +import { render, waitFor, screen, fireEvent } from '@testing-library/react'; -import { FormDataConsumerView } from './FormDataConsumer'; +import FormDataConsumer, { FormDataConsumerView } from './FormDataConsumer'; +import { testDataProvider } from '../dataProvider'; +import { + AdminContext, + BooleanInput, + SimpleForm, + TextInput, + SimpleFormIterator, + ArrayInput, +} from 'ra-ui-materialui'; +import expect from 'expect'; describe('FormDataConsumerView', () => { it('does not call its children function with scopedFormData and getSource if it did not receive an index prop', () => { @@ -26,6 +36,7 @@ describe('FormDataConsumerView', () => { it('calls its children function with scopedFormData and getSource if it received an index prop', () => { const children = jest.fn(({ getSource }) => { getSource('id'); + return null; }); const formData = { id: 123, title: 'A title', authors: [{ id: 0 }] }; @@ -46,4 +57,109 @@ describe('FormDataConsumerView', () => { 'authors[0].id' ); }); + + it('calls its children with updated formData on first render', async () => { + let globalFormData; + render( + + + + + {({ formData, ...rest }) => { + globalFormData = formData; + + return ; + }} + + + + ); + + await waitFor(() => { + expect(globalFormData).toEqual({ hi: true, bye: undefined }); + }); + }); + + it('should be reactive', async () => { + render( + + + + + {({ formData, ...rest }) => + !formData.hi ? ( + + ) : null + } + + + + ); + + await waitFor(() => { + expect( + screen.queryByLabelText('resources.undefined.fields.bye') + ).toBeNull(); + }); + + fireEvent.click(screen.getByLabelText('resources.undefined.fields.hi')); + + await waitFor(() => { + expect( + screen.getByLabelText('resources.undefined.fields.bye') + ).not.toBeNull(); + }); + }); + + it('calls its children with updated scopedFormData when inside an ArrayInput', async () => { + let globalScopedFormData; + render( + + + + + + + {({ + formData, + scopedFormData, + getSource, + ...rest + }) => { + globalScopedFormData = scopedFormData; + return scopedFormData && + scopedFormData.name ? ( + + ) : null; + }} + + + + + + ); + + expect(globalScopedFormData).toEqual(undefined); + + fireEvent.click(screen.getByLabelText('ra.action.add')); + + expect(globalScopedFormData).toEqual({ name: '' }); + + fireEvent.change( + screen.getByLabelText('resources.undefined.fields.authors.name'), + { + target: { value: 'a' }, + } + ); + + await waitFor(() => { + expect(globalScopedFormData).toEqual({ + name: 'a', + role: undefined, + }); + }); + }); }); diff --git a/packages/ra-core/src/form/FormDataConsumer.tsx b/packages/ra-core/src/form/FormDataConsumer.tsx index 519e3ea80ce..3f4d9668e89 100644 --- a/packages/ra-core/src/form/FormDataConsumer.tsx +++ b/packages/ra-core/src/form/FormDataConsumer.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { ReactNode } from 'react'; -import { useWatch } from 'react-hook-form'; +import { useWatch, useFormContext, FieldValues } from 'react-hook-form'; import get from 'lodash/get'; import warning from '../util/warning'; @@ -43,22 +43,27 @@ import warning from '../util/warning'; * ); */ const FormDataConsumer = (props: ConnectedProps) => { - const formData = useWatch(); + const { getValues } = useFormContext(); + let formData = useWatch(); + + //useWatch will initially return the provided defaultValues of the form. + //We must get the initial formData from getValues + if (Object.keys(formData).length === 0) { + (formData as FieldValues) = getValues(); + } return ; }; export const FormDataConsumerView = (props: Props) => { const { children, form, formData, source, index, ...rest } = props; - let scopedFormData = formData; - let getSource; let getSourceHasBeenCalled = false; let ret; // If we have an index, we are in an iterator like component (such as the SimpleFormIterator) if (typeof index !== 'undefined' && source) { - scopedFormData = get(formData, source); - getSource = (scopedSource: string) => { + const scopedFormData = get(formData, source); + const getSource = (scopedSource: string) => { getSourceHasBeenCalled = true; return `${source}.${scopedSource}`; };