From bb914fe335ad41f3fcde264643d0d66c9332fc7e Mon Sep 17 00:00:00 2001 From: bill Date: Sun, 25 Feb 2024 10:34:00 +1100 Subject: [PATCH] seperate proxy subscription between hook and subscribe function --- src/__tests__/useForm/subscribe.test.tsx | 69 +++++++++++++++++++++++- src/logic/createFormControl.ts | 60 +++++++++++++++------ 2 files changed, 111 insertions(+), 18 deletions(-) diff --git a/src/__tests__/useForm/subscribe.test.tsx b/src/__tests__/useForm/subscribe.test.tsx index eab3ece697b..846227ca9f3 100644 --- a/src/__tests__/useForm/subscribe.test.tsx +++ b/src/__tests__/useForm/subscribe.test.tsx @@ -1,14 +1,19 @@ +import React from 'react'; +import { render, screen, waitFor } from '@testing-library/react'; + import { createFormControl } from '../../logic'; +import { useForm } from '../../useForm'; describe('subscribe', () => { - it('should subscribe to correct form state', () => { + it('should subscribe to correct form state and prevent form re-render', async () => { const callback = jest.fn(); - const { subscribe, register, setValue } = createFormControl({ + const control = createFormControl({ defaultValues: { test: '', }, }); + const { subscribe, register, setValue } = control; subscribe({ formState: { @@ -39,6 +44,66 @@ describe('subscribe', () => { disabled: false, name: 'test', }); + + let renderCount = 0; + + const App = () => { + useForm({ + control, + }); + + renderCount++; + + return

{renderCount === 1 ? 'yes' : 'no'}

; + }; + + render(); + + await waitFor(() => { + screen.getByText('yes'); + }); + }); + + it('should re-render by hook form state subscription', async () => { + const callback = jest.fn(); + + const control = createFormControl({ + defaultValues: { + test: '', + }, + }); + const { subscribe, register, setValue } = control; + + subscribe({ + formState: { + values: true, + }, + callback, + }); + + register('test'); + setValue('test', 'data', { shouldDirty: true }); + + let renderCount = 0; + + const App = () => { + const { + formState: { isDirty }, + } = useForm({ + control, + }); + + isDirty; + renderCount++; + + return

{renderCount === 2 ? 'yes' : 'no'}

; + }; + + render(); + + await waitFor(() => { + screen.getByText('yes'); + }); }); it('should only subscribe to individual field', () => { diff --git a/src/logic/createFormControl.ts b/src/logic/createFormControl.ts index 944783953c5..7ca8f733118 100644 --- a/src/logic/createFormControl.ts +++ b/src/logic/createFormControl.ts @@ -142,7 +142,7 @@ export function createFormControl< }; let delayErrorCallback: DelayCallback | null; let timer = 0; - let _proxyFormState: ReadFormState = { + const _proxyFormState: ReadFormState = { isDirty: false, dirtyFields: false, validatingFields: false, @@ -151,6 +151,9 @@ export function createFormControl< isValid: false, errors: false, }; + let _proxySubscribeFormState = { + ..._proxyFormState, + }; const _subjects: Subjects = { array: createSubject(), state: createSubject(), @@ -168,7 +171,11 @@ export function createFormControl< }; const _updateValid = async (shouldUpdateValid?: boolean) => { - if (_proxyFormState.isValid || shouldUpdateValid) { + if ( + _proxyFormState.isValid || + _proxySubscribeFormState.isValid || + shouldUpdateValid + ) { const isValid = _options.resolver ? isEmptyObject((await _executeSchema()).errors) : await executeBuiltInValidation(_fields, true); @@ -182,7 +189,14 @@ export function createFormControl< }; const _updateIsValidating = (isValidating: boolean, names: string[]) => { - if (!(_proxyFormState.isValidating || _proxyFormState.validatingFields)) { + if ( + !( + _proxyFormState.isValidating || + _proxyFormState.validatingFields || + _proxySubscribeFormState.isValidating || + _proxySubscribeFormState.validatingFields + ) + ) { return; } names.forEach((name) => { @@ -224,7 +238,8 @@ export function createFormControl< } if ( - _proxyFormState.touchedFields && + (_proxyFormState.touchedFields || + _proxySubscribeFormState.touchedFields) && shouldUpdateFieldsAndState && Array.isArray(get(_formState.touchedFields, name)) ) { @@ -236,7 +251,7 @@ export function createFormControl< shouldSetValues && set(_formState.touchedFields, name, touchedFields); } - if (_proxyFormState.dirtyFields) { + if (_proxyFormState.dirtyFields || _proxySubscribeFormState.dirtyFields) { _formState.dirtyFields = getDirtyFields(_defaultValues, _formValues); } @@ -315,7 +330,7 @@ export function createFormControl< ); if (!isBlurEvent || shouldDirty) { - if (_proxyFormState.isDirty) { + if (_proxyFormState.isDirty || _proxySubscribeFormState.isDirty) { isPreviousDirty = _formState.isDirty; _formState.isDirty = output.isDirty = _getDirty(); shouldUpdateField = isPreviousDirty !== output.isDirty; @@ -331,7 +346,8 @@ export function createFormControl< output.dirtyFields = _formState.dirtyFields; shouldUpdateField = shouldUpdateField || - (_proxyFormState.dirtyFields && + ((_proxyFormState.dirtyFields || + _proxySubscribeFormState.dirtyFields) && isPreviousDirty !== !isCurrentFieldPristine); } @@ -343,7 +359,8 @@ export function createFormControl< output.touchedFields = _formState.touchedFields; shouldUpdateField = shouldUpdateField || - (_proxyFormState.touchedFields && + ((_proxyFormState.touchedFields || + _proxySubscribeFormState.touchedFields) && isPreviousFieldTouched !== isBlurEvent); } } @@ -365,7 +382,7 @@ export function createFormControl< ) => { const previousFieldError = get(_formState.errors, name); const shouldUpdateValid = - _proxyFormState.isValid && + (_proxyFormState.isValid || _proxySubscribeFormState.isValid) && isBoolean(isValid) && _formState.isValid !== isValid; @@ -657,7 +674,10 @@ export function createFormControl< }); if ( - (_proxyFormState.isDirty || _proxyFormState.dirtyFields) && + (_proxyFormState.isDirty || + _proxyFormState.dirtyFields || + _proxySubscribeFormState.isDirty || + _proxySubscribeFormState.dirtyFields) && options.shouldDirty ) { _subjects.state.next({ @@ -738,7 +758,8 @@ export function createFormControl< }); if (shouldSkipValidation) { - _proxyFormState.isValid && _updateValid(); + (_proxyFormState.isValid || _proxySubscribeFormState.isValid) && + _updateValid(); return ( shouldRender && @@ -787,7 +808,10 @@ export function createFormControl< if (isFieldValueUpdated) { if (error) { isValid = false; - } else if (_proxyFormState.isValid) { + } else if ( + _proxyFormState.isValid || + _proxySubscribeFormState.isValid + ) { isValid = await executeBuiltInValidation(_fields, true); } } @@ -847,7 +871,8 @@ export function createFormControl< _subjects.state.next({ ...(!isString(name) || - (_proxyFormState.isValid && isValid !== _formState.isValid) + ((_proxyFormState.isValid || _proxySubscribeFormState.isValid) && + isValid !== _formState.isValid) ? {} : { name }), ...(_options.resolver || !name ? { isValid } : {}), @@ -975,11 +1000,14 @@ export function createFormControl< const subscribe: UseFromSubscribe = (props) => { _state.mount = true; - _proxyFormState = { - ..._proxyFormState, + _proxySubscribeFormState = { + ..._proxySubscribeFormState, ...props.formState, }; - return _subscribe(props); + return _subscribe({ + ...props, + formState: _proxySubscribeFormState, + }); }; const unregister: UseFormUnregister = (name, options = {}) => {