Skip to content

Commit

Permalink
Fix useFormGroup does not reflect its fields state
Browse files Browse the repository at this point in the history
  • Loading branch information
djhi committed Jun 20, 2024
1 parent 30c1858 commit 988f588
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 9 deletions.
25 changes: 24 additions & 1 deletion packages/ra-core/src/form/useFormGroup.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,14 @@ describe('useFormGroup', () => {
isValid: true,
isDirty: false,
isTouched: false,
isValidating: false,
name: 'title',
},
{
isValid: false,
isDirty: true,
isTouched: true,
isValidating: false,
error: 'Invalid',
name: 'description',
},
Expand All @@ -36,6 +38,7 @@ describe('useFormGroup', () => {
isValid: false,
isDirty: true,
isTouched: true,
isValidating: false,
errors: {
description: 'Invalid',
},
Expand All @@ -48,19 +51,22 @@ describe('useFormGroup', () => {
isValid: true,
isDirty: false,
isTouched: false,
isValidating: false,
name: 'title',
},
{
isValid: true,
isDirty: false,
isTouched: false,
isValidating: false,
name: 'description',
},
],
{
isValid: true,
isDirty: false,
isTouched: false,
isValidating: false,
errors: {},
},
],
Expand All @@ -71,19 +77,22 @@ describe('useFormGroup', () => {
isValid: true,
isDirty: false,
isTouched: false,
isValidating: false,
name: 'title',
},
{
isValid: true,
isDirty: true,
isTouched: true,
isValidating: false,
name: 'description',
},
],
{
isValid: true,
isDirty: true,
isTouched: true,
isValidating: false,
errors: {},
},
],
Expand All @@ -103,7 +112,7 @@ describe('useFormGroup', () => {

render(
<AdminContext dataProvider={testDataProvider()}>
<SimpleForm>
<SimpleForm mode="onChange">
<FormGroupContextProvider name="simplegroup">
<IsDirty />
<TextInput source="url" />
Expand All @@ -118,20 +127,32 @@ describe('useFormGroup', () => {
isDirty: false,
isTouched: false,
isValid: true,
isValidating: false,
});
});

const input = screen.getByLabelText('resources.undefined.fields.url');
fireEvent.change(input, {
target: { value: 'test' },
});
await waitFor(() => {
expect(state).toEqual({
errors: {},
isDirty: true,
isTouched: false,
isValid: true,
isValidating: false,
});
});
// This is coherent with how react-hook-form works, inputs are only touched when they lose focus
fireEvent.blur(input);
await waitFor(() => {
expect(state).toEqual({
errors: {},
isDirty: true,
isTouched: true,
isValid: true,
isValidating: false,
});
});
});
Expand Down Expand Up @@ -176,6 +197,7 @@ describe('useFormGroup', () => {
isDirty: false,
isTouched: false,
isValid: true,
isValidating: false,
});
});

Expand All @@ -190,6 +212,7 @@ describe('useFormGroup', () => {
isDirty: true,
isTouched: false,
isValid: true,
isValidating: false,
});
});
});
Expand Down
39 changes: 31 additions & 8 deletions packages/ra-core/src/form/useFormGroup.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
import { useCallback, useEffect, useState } from 'react';
import { useEffect, useState } from 'react';
import get from 'lodash/get';
import isEqual from 'lodash/isEqual';
import { useFormState } from 'react-hook-form';
import { useFormGroups } from './useFormGroups';
import { useEvent } from '../util';

type FieldState = {
name: string;
error?: any;
isDirty: boolean;
isTouched: boolean;
isValid: boolean;
isValidating: boolean;
};

type FormGroupState = {
errors?: object;
isDirty: boolean;
isTouched: boolean;
isValid: boolean;
isValidating: boolean;
};

/**
Expand Down Expand Up @@ -63,16 +66,27 @@ type FormGroupState = {
* @returns {FormGroupState} The form group state
*/
export const useFormGroup = (name: string): FormGroupState => {
const { dirtyFields, touchedFields, errors } = useFormState();
const { dirtyFields, touchedFields, validatingFields, errors } =
useFormState();

// dirtyFields, touchedFields and validatingFields are objects with keys being the field names
// Ex: { title: true }
// However, they are not correctly serialized when using JSON.stringify
// To avoid our effects to not be triggered when they should, we extract the keys and use that as a dependency
const dirtyFieldsNames = Object.keys(dirtyFields);
const touchedFieldsNames = Object.keys(touchedFields);
const validatingFieldsNames = Object.keys(validatingFields);

const formGroups = useFormGroups();
const [state, setState] = useState<FormGroupState>({
errors: undefined,
isDirty: false,
isTouched: false,
isValid: true,
isValidating: true,
});

const updateGroupState = useCallback(() => {
const updateGroupState = useEvent(() => {
if (!formGroups) return;
const fields = formGroups.getGroupFields(name);
const fieldStates = fields
Expand All @@ -81,7 +95,9 @@ export const useFormGroup = (name: string): FormGroupState => {
name: field,
error: get(errors, field, undefined),
isDirty: get(dirtyFields, field, false) !== false,
isValid: get(errors, field, undefined) == undefined, // eslint-disable-line
isValid: get(errors, field, undefined) == null,
isValidating:
get(validatingFields, field, undefined) == null,
isTouched: get(touchedFields, field, false) !== false,
};
})
Expand All @@ -95,16 +111,21 @@ export const useFormGroup = (name: string): FormGroupState => {

return oldState;
});
}, [dirtyFields, errors, touchedFields, formGroups, name]);
});

useEffect(
() => {
updateGroupState();
},
// eslint-disable-next-line
// eslint-disable-next-line react-hooks/exhaustive-deps
[
// eslint-disable-next-line
JSON.stringify({ dirtyFields, errors, touchedFields }),
// eslint-disable-next-line react-hooks/exhaustive-deps
JSON.stringify(dirtyFieldsNames),
errors,
// eslint-disable-next-line react-hooks/exhaustive-deps
JSON.stringify(touchedFieldsNames),
// eslint-disable-next-line react-hooks/exhaustive-deps
JSON.stringify(validatingFieldsNames),
updateGroupState,
]
);
Expand Down Expand Up @@ -144,6 +165,7 @@ export const getFormGroupState = (
errors,
isTouched: acc.isTouched || fieldState.isTouched,
isValid: acc.isValid && fieldState.isValid,
isValidating: acc.isValidating && fieldState.isValidating,
};

return newState;
Expand All @@ -153,6 +175,7 @@ export const getFormGroupState = (
errors: undefined,
isValid: true,
isTouched: false,
isValidating: false,
}
);
};

0 comments on commit 988f588

Please sign in to comment.