Skip to content

Commit

Permalink
feat(conform-react): stop relying on the state snapshot
Browse files Browse the repository at this point in the history
  • Loading branch information
edmundhung committed Feb 20, 2024
1 parent c5f2b48 commit f9092fa
Show file tree
Hide file tree
Showing 2 changed files with 32 additions and 49 deletions.
49 changes: 24 additions & 25 deletions packages/conform-react/context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -140,17 +140,25 @@ export function useFormContext<Schema extends Record<string, any>, FormError>(
return form as unknown as FormContext<Schema, FormError, unknown>;
}

export function useFormState<FormError>(
export function useFormSubscription<FormError>(
form: FormContext<any, FormError>,
subjectRef?: MutableRefObject<SubscriptionSubject>,
): FormState<FormError> {
initialSubject: SubscriptionSubject = {},
): MutableRefObject<SubscriptionSubject> {
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: {
Expand Down Expand Up @@ -180,18 +188,6 @@ export function FormStateInput(props: { formId?: string }): React.ReactElement {
);
}

export function useSubjectRef(
initialSubject: SubscriptionSubject = {},
): MutableRefObject<SubscriptionSubject> {
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<SubscriptionSubject>,
name: string,
Expand All @@ -214,8 +210,9 @@ export function getMetadata<
FormSchema extends Record<string, any>,
>(
formId: FormId<FormSchema, FormError>,
state: FormState<FormError>,
context: FormContext<FormSchema, FormError, any>,
subjectRef: MutableRefObject<SubscriptionSubject>,
state: FormState<FormError>,
name: FieldName<Schema, FormSchema, FormError> = '',
): Metadata<Schema, FormSchema, FormError> {
const id = name ? `${formId}-${name}` : formId;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -305,7 +302,7 @@ export function getFieldMetadata<
FormError,
>(
formId: FormId<FormSchema, FormError>,
state: FormState<FormError>,
context: FormContext<FormSchema, FormError, any>,
subjectRef: MutableRefObject<SubscriptionSubject>,
prefix = '',
key?: string | number,
Expand All @@ -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;
Expand Down Expand Up @@ -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),
);
};
}
Expand All @@ -362,15 +361,15 @@ export function getFormMetadata<
FormValue = Schema,
>(
formId: FormId<Schema, FormError>,
state: FormState<FormError>,
subjectRef: MutableRefObject<SubscriptionSubject>,
context: FormContext<Schema, FormError, FormValue>,
subjectRef: MutableRefObject<SubscriptionSubject>,
noValidate: boolean,
): FormMetadata<Schema, FormError> {
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 {
Expand Down
32 changes: 8 additions & 24 deletions packages/conform-react/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,8 @@ import {
type Pretty,
type FormOptions,
createFormContext,
useFormState,
useFormSubscription,
useFormContext,
useSubjectRef,
getFieldMetadata,
getFormMetadata,
} from './context';
Expand Down Expand Up @@ -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()];
}
Expand All @@ -106,18 +104,11 @@ export function useFormMetadata<
defaultNoValidate?: boolean;
} = {},
): FormMetadata<Schema, FormError> {
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<
Expand All @@ -133,22 +124,15 @@ export function useField<
FieldMetadata<FieldSchema, FormSchema, FormError>,
FormMetadata<FormSchema, FormError>,
] {
const subjectRef = useSubjectRef();
const context = useFormContext(options.formId);
const state = useFormState(context, subjectRef);
const subjectRef = useFormSubscription(context);
const field = getFieldMetadata<FieldSchema, FormSchema, FormError>(
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];
}

0 comments on commit f9092fa

Please sign in to comment.