Skip to content

Commit

Permalink
🐞 fix #9773 useFormState missing state update (#9777)
Browse files Browse the repository at this point in the history
* 🐞 fix #9773 useFormState missing state update

* update api extrator

* fix automation caught issue
  • Loading branch information
bluebill1049 committed Jan 15, 2023
1 parent 3b2dee3 commit 0f3e19e
Show file tree
Hide file tree
Showing 8 changed files with 88 additions and 18 deletions.
10 changes: 5 additions & 5 deletions cypress/e2e/useFormState.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,9 @@ describe('useFormState', () => {
'maxDate',
'minLength',
],
isSubmitted: false,
isSubmitted: true,
isSubmitSuccessful: false,
submitCount: 0,
submitCount: 1,
isValid: false,
}),
);
Expand Down Expand Up @@ -93,9 +93,9 @@ describe('useFormState', () => {
'minLength',
'minRequiredLength',
],
isSubmitted: false,
isSubmitted: true,
isSubmitSuccessful: false,
submitCount: 0,
submitCount: 1,
isValid: true,
}),
);
Expand Down Expand Up @@ -135,7 +135,7 @@ describe('useFormState', () => {
],
isSubmitted: true,
isSubmitSuccessful: true,
submitCount: 1,
submitCount: 2,
isValid: true,
}),
);
Expand Down
1 change: 1 addition & 0 deletions reports/api-extractor.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export type Control<TFieldValues extends FieldValues = FieldValues, TContext = a
_getDirty: GetIsDirty;
_formState: FormState<TFieldValues>;
_updateValid: (shouldUpdateValid?: boolean) => void;
_updateFormState: (formState: Partial<FormState<TFieldValues>>) => void;
_fields: FieldRefs;
_formValues: FieldValues;
_proxyFormState: ReadFormState;
Expand Down
33 changes: 33 additions & 0 deletions src/__tests__/useFormState.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -744,4 +744,37 @@ describe('useFormState', () => {
expect(await screen.findByText('dirty')).toBeVisible();
expect(await screen.findByText('valid')).toBeVisible();
});

it('should subscribe and update formState', async () => {
function App() {
const { register, control, handleSubmit } = useForm({
defaultValues: {
firstName: '',
},
});
const { errors } = useFormState({ control });

return (
<form onSubmit={handleSubmit(() => {})}>
<input {...register('firstName', { required: 'Required' })} />
<p>{errors.firstName?.message}</p>
<button>Submit</button>
</form>
);
}

render(<App />);

fireEvent.click(screen.getByRole('button'));

waitFor(() => screen.getByText('Required'));

fireEvent.change(screen.getByRole('textbox'), {
target: { value: 'data' },
});

waitFor(() =>
expect(screen.queryByText('Required')).not.toBeInTheDocument(),
);
});
});
10 changes: 10 additions & 0 deletions src/logic/createFormControl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1226,6 +1226,15 @@ export function createFormControl<
}
};

const _updateFormState = (
updatedFormState: Partial<FormState<TFieldValues>>,
) => {
_formState = {
..._formState,
...updatedFormState,
};
};

if (isFunction(_options.defaultValues)) {
_options.defaultValues().then((values) => {
reset(values, _options.resetOptions);
Expand All @@ -1249,6 +1258,7 @@ export function createFormControl<
_updateFieldArray,
_getFieldArray,
_reset,
_updateFormState,
_subjects,
_proxyFormState,
get _fields() {
Expand Down
14 changes: 11 additions & 3 deletions src/logic/shouldRenderFormState.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
import { VALIDATION_MODE } from '../constants';
import { ReadFormState } from '../types';
import {
Control,
FieldValues,
FormState,
InternalFieldName,
ReadFormState,
} from '../types';
import isEmptyObject from '../utils/isEmptyObject';

export default <T extends Record<string, any>, K extends ReadFormState>(
formStateData: T,
export default <T extends FieldValues, K extends ReadFormState>(
formStateData: Partial<FormState<T>> & { name?: InternalFieldName },
_proxyFormState: K,
updateFormState: Control<T>['_updateFormState'],
isRoot?: boolean,
) => {
updateFormState(formStateData);
const { name, ...formState } = formStateData;

return (
Expand Down
1 change: 1 addition & 0 deletions src/types/form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -748,6 +748,7 @@ export type Control<
_getDirty: GetIsDirty;
_formState: FormState<TFieldValues>;
_updateValid: (shouldUpdateValid?: boolean) => void;
_updateFormState: (formState: Partial<FormState<TFieldValues>>) => void;
_fields: FieldRefs;
_formValues: FieldValues;
_proxyFormState: ReadFormState;
Expand Down
26 changes: 18 additions & 8 deletions src/useForm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,13 @@ import getProxyFormState from './logic/getProxyFormState';
import shouldRenderFormState from './logic/shouldRenderFormState';
import deepEqual from './utils/deepEqual';
import isFunction from './utils/isFunction';
import { FieldValues, FormState, UseFormProps, UseFormReturn } from './types';
import {
FieldValues,
FormState,
InternalFieldName,
UseFormProps,
UseFormReturn,
} from './types';
import { useSubscribe } from './useSubscribe';

/**
Expand Down Expand Up @@ -77,13 +83,17 @@ export function useForm<

useSubscribe({
subject: control._subjects.state,
next: (value: FieldValues) => {
if (shouldRenderFormState(value, control._proxyFormState, true)) {
control._formState = {
...control._formState,
...value,
};

next: (
value: Partial<FormState<TFieldValues>> & { name?: InternalFieldName },
) => {
if (
shouldRenderFormState(
value,
control._proxyFormState,
control._updateFormState,
true,
)
) {
updateFormState({ ...control._formState });
}
},
Expand Down
11 changes: 9 additions & 2 deletions src/useFormState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import shouldRenderFormState from './logic/shouldRenderFormState';
import shouldSubscribeByName from './logic/shouldSubscribeByName';
import {
FieldValues,
FormState,
InternalFieldName,
UseFormStateProps,
UseFormStateReturn,
Expand Down Expand Up @@ -64,14 +65,20 @@ function useFormState<TFieldValues extends FieldValues = FieldValues>(

useSubscribe({
disabled,
next: (value: { name?: InternalFieldName }) =>
next: (
value: Partial<FormState<TFieldValues>> & { name?: InternalFieldName },
) =>
_mounted.current &&
shouldSubscribeByName(
_name.current as InternalFieldName,
value.name,
exact,
) &&
shouldRenderFormState(value, _localProxyFormState.current) &&
shouldRenderFormState(
value,
_localProxyFormState.current,
control._updateFormState,
) &&
updateFormState({
...control._formState,
...value,
Expand Down

0 comments on commit 0f3e19e

Please sign in to comment.