diff --git a/packages/conform-dom/form.ts b/packages/conform-dom/form.ts index 26a09a3f..e645ac21 100644 --- a/packages/conform-dom/form.ts +++ b/packages/conform-dom/form.ts @@ -129,6 +129,7 @@ export interface Form = any> { getSubject?: () => SubscriptionSubject | undefined, ): () => void; getContext(): FormContext; + getSerializedState(): string; } export const VALIDATION_UNDEFINED = '__undefined__'; @@ -397,6 +398,13 @@ export function createForm = any>( return element; } + function getSerializedState(): string { + return JSON.stringify({ + key: context.state.key, + validated: context.state.validated, + }); + } + function submit(event: SubmitEvent) { const form = event.target as HTMLFormElement; const submitter = event.submitter as @@ -412,10 +420,7 @@ export function createForm = any>( const input = getStateInput(form); // To ensure it capturing latest state before parsing - input.value = JSON.stringify({ - key: context.state.key, - validated: context.state.validated, - }); + input.value = getSerializedState(); const formData = getFormData(form, submitter); const result = { @@ -673,5 +678,6 @@ export function createForm = any>( update, subscribe, getContext, + getSerializedState, }; } diff --git a/packages/conform-react/context.tsx b/packages/conform-react/context.tsx index c13221c7..4cf9488c 100644 --- a/packages/conform-react/context.tsx +++ b/packages/conform-react/context.tsx @@ -51,33 +51,41 @@ export interface FieldConfig extends BaseConfig { export const Context = createContext>({}); -export function useFormContext( - formId: string, - localContext?: Form | undefined, - subjectRef?: MutableRefObject, -): FormContext { +export function useContextForm(formId: string, context?: Form) { const registry = useContext(Context); - const form = localContext ?? registry[formId]; + const form = context ?? registry[formId]; if (!form) { throw new Error('Form context is not available'); } + return form; +} + +export function useFormContext( + formId: string, + context?: Form, + subjectRef?: MutableRefObject, +): FormContext { + const form = useContextForm(formId, context); const subscribe = useCallback( (callback: () => void) => form.subscribe(callback, () => subjectRef?.current), [form, subjectRef], ); - const context = useSyncExternalStore( + const result = useSyncExternalStore( subscribe, form.getContext, form.getContext, ); - return context; + return result; } -export function ConformBoundary(props: { context: Form; children: ReactNode }) { +export function FormContextProvider(props: { + context: Form; + children: ReactNode; +}): ReactNode { const context = useContext(Context); const value = useMemo( () => ({ ...context, [props.context.id]: props.context }), @@ -87,20 +95,24 @@ export function ConformBoundary(props: { context: Form; children: ReactNode }) { return {props.children}; } -export function FormStateInput(props: { - formId: string; - context?: Form; -}): React.ReactElement { - const context = useFormContext(props.formId, props.context); +export function FormStateInput( + props: + | { + formId: string; + context?: undefined; + } + | { + formId?: undefined; + context: Form; + }, +): React.ReactElement { + const form = useContextForm(props.formId ?? props.context.id, props.context); return ( ); diff --git a/packages/conform-react/hooks.ts b/packages/conform-react/hooks.ts index f5fe56a1..7e93273e 100644 --- a/packages/conform-react/hooks.ts +++ b/packages/conform-react/hooks.ts @@ -197,35 +197,25 @@ export function useForm>(options: { return { context: form, fields, - form: { - id: formId, - errorId: config.errorId, - descriptionId: config.descriptionId, - onSubmit, - onReset, - noValidate, - get defaultValue() { - return config.defaultValue; + form: new Proxy(config as any, { + get(target, key, receiver) { + switch (key) { + case 'onSubmit': + return onSubmit; + case 'onReset': + return onReset; + case 'noValidate': + return noValidate; + case 'key': + case 'formId': + case 'name': + case 'constraint': + return; + } + + return Reflect.get(target, key, receiver); }, - get value() { - return config.value; - }, - get dirty() { - return config.dirty; - }, - get valid() { - return config.valid; - }, - get error() { - return config.error; - }, - get allError() { - return config.allError; - }, - get allValid() { - return config.allValid; - }, - }, + }), }; } diff --git a/packages/conform-react/index.ts b/packages/conform-react/index.ts index f2922302..15a7da94 100644 --- a/packages/conform-react/index.ts +++ b/packages/conform-react/index.ts @@ -7,7 +7,7 @@ export { export { type Field, type FieldConfig, - ConformBoundary, + FormContextProvider, FormStateInput, } from './context'; export { useForm, useFieldset, useFieldList, useField } from './hooks'; diff --git a/playground/app/routes/simple-list.tsx b/playground/app/routes/simple-list.tsx index a187190e..aa1a56f1 100644 --- a/playground/app/routes/simple-list.tsx +++ b/playground/app/routes/simple-list.tsx @@ -56,12 +56,12 @@ export default function SimpleList() { const items = useFieldList({ formId: form.id, name: fields.items.name, - context: context, + context, }); return (
- +