From f9092fa4dc73ef930a4d5726f40d731dd28d1f3f Mon Sep 17 00:00:00 2001 From: Edmund Hung Date: Tue, 20 Feb 2024 18:12:49 +0100 Subject: [PATCH] feat(conform-react): stop relying on the state snapshot --- packages/conform-react/context.tsx | 49 +++++++++++++++--------------- packages/conform-react/hooks.ts | 32 +++++-------------- 2 files changed, 32 insertions(+), 49 deletions(-) diff --git a/packages/conform-react/context.tsx b/packages/conform-react/context.tsx index 77ca260c..ed7c8c2f 100644 --- a/packages/conform-react/context.tsx +++ b/packages/conform-react/context.tsx @@ -140,17 +140,25 @@ export function useFormContext, FormError>( return form as unknown as FormContext; } -export function useFormState( +export function useFormSubscription( form: FormContext, - subjectRef?: MutableRefObject, -): FormState { + initialSubject: SubscriptionSubject = {}, +): MutableRefObject { + const subjectRef = useRef(initialSubject); const subscribe = useCallback( (callback: () => void) => form.subscribe(callback, () => subjectRef?.current), [form, subjectRef], ); - return useSyncExternalStore(subscribe, form.getState, form.getState); + // Subscribe to the form context based on the subject + useSyncExternalStore(subscribe, form.getState, form.getState); + + // Reset the subject everytime the component is rerendered + // This let us subscribe to data used in the last render only + subjectRef.current = initialSubject; + + return subjectRef; } export function FormProvider(props: { @@ -180,18 +188,6 @@ export function FormStateInput(props: { formId?: string }): React.ReactElement { ); } -export function useSubjectRef( - initialSubject: SubscriptionSubject = {}, -): MutableRefObject { - const subjectRef = useRef(initialSubject); - - // Reset the subject everytime the component is rerendered - // This let us subscribe to data used in the last render only - subjectRef.current = initialSubject; - - return subjectRef; -} - export function updateSubjectRef( ref: MutableRefObject, name: string, @@ -214,8 +210,9 @@ export function getMetadata< FormSchema extends Record, >( formId: FormId, - state: FormState, + context: FormContext, subjectRef: MutableRefObject, + state: FormState, name: FieldName = '', ): Metadata { const id = name ? `${formId}-${name}` : formId; @@ -264,7 +261,7 @@ export function getMetadata< new Proxy({} as any, { get(target, key, receiver) { if (typeof key === 'string') { - return getFieldMetadata(formId, state, subjectRef, name, key); + return getFieldMetadata(formId, context, subjectRef, name, key); } return Reflect.get(target, key, receiver); @@ -305,7 +302,7 @@ export function getFieldMetadata< FormError, >( formId: FormId, - state: FormState, + context: FormContext, subjectRef: MutableRefObject, prefix = '', key?: string | number, @@ -314,10 +311,12 @@ export function getFieldMetadata< typeof key === 'undefined' ? prefix : formatPaths([...getPaths(prefix), key]); - const metadata = getMetadata(formId, state, subjectRef, name); return new Proxy({} as any, { get(_, key, receiver) { + const state = context.getState(); + const metadata = getMetadata(formId, context, subjectRef, state, name); + switch (key) { case 'formId': return formId; @@ -345,7 +344,7 @@ export function getFieldMetadata< return Array(initialValue.length) .fill(0) .map((_, index) => - getFieldMetadata(formId, state, subjectRef, name, index), + getFieldMetadata(formId, context, subjectRef, name, index), ); }; } @@ -362,15 +361,15 @@ export function getFormMetadata< FormValue = Schema, >( formId: FormId, - state: FormState, - subjectRef: MutableRefObject, context: FormContext, + subjectRef: MutableRefObject, noValidate: boolean, ): FormMetadata { - const metadata = getMetadata(formId, state, subjectRef); - return new Proxy({} as any, { get(_, key, receiver) { + const state = context.getState(); + const metadata = getMetadata(formId, context, subjectRef, state); + switch (key) { case 'context': return { diff --git a/packages/conform-react/hooks.ts b/packages/conform-react/hooks.ts index c42dcf61..bd6cb0c5 100644 --- a/packages/conform-react/hooks.ts +++ b/packages/conform-react/hooks.ts @@ -6,9 +6,8 @@ import { type Pretty, type FormOptions, createFormContext, - useFormState, + useFormSubscription, useFormContext, - useSubjectRef, getFieldMetadata, getFormMetadata, } from './context'; @@ -89,10 +88,9 @@ export function useForm< context.onUpdate({ ...formConfig, formId }); }); - const subjectRef = useSubjectRef(); - const state = useFormState(context, subjectRef); + const subjectRef = useFormSubscription(context); const noValidate = useNoValidate(options.defaultNoValidate); - const form = getFormMetadata(formId, state, subjectRef, context, noValidate); + const form = getFormMetadata(formId, context, subjectRef, noValidate); return [form, form.getFieldset()]; } @@ -106,18 +104,11 @@ export function useFormMetadata< defaultNoValidate?: boolean; } = {}, ): FormMetadata { - const subjectRef = useSubjectRef(); const context = useFormContext(formId); - const state = useFormState(context, subjectRef); + const subjectRef = useFormSubscription(context); const noValidate = useNoValidate(options.defaultNoValidate); - return getFormMetadata( - context.formId, - state, - subjectRef, - context, - noValidate, - ); + return getFormMetadata(context.formId, context, subjectRef, noValidate); } export function useField< @@ -133,22 +124,15 @@ export function useField< FieldMetadata, FormMetadata, ] { - const subjectRef = useSubjectRef(); const context = useFormContext(options.formId); - const state = useFormState(context, subjectRef); + const subjectRef = useFormSubscription(context); const field = getFieldMetadata( context.formId, - state, + context, subjectRef, name, ); - const form = getFormMetadata( - context.formId, - state, - subjectRef, - context, - false, - ); + const form = getFormMetadata(context.formId, context, subjectRef, false); return [field, form]; }