Skip to content

Commit

Permalink
feat!: rename ConformBoundary to FormContextProvider
Browse files Browse the repository at this point in the history
  • Loading branch information
edmundhung committed Nov 12, 2023
1 parent f99d57c commit e5c87a8
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 53 deletions.
14 changes: 10 additions & 4 deletions packages/conform-dom/form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ export interface Form<Type extends Record<string, unknown> = any> {
getSubject?: () => SubscriptionSubject | undefined,
): () => void;
getContext(): FormContext;
getSerializedState(): string;
}

export const VALIDATION_UNDEFINED = '__undefined__';
Expand Down Expand Up @@ -397,6 +398,13 @@ export function createForm<Type extends Record<string, unknown> = 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
Expand All @@ -412,10 +420,7 @@ export function createForm<Type extends Record<string, unknown> = 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 = {
Expand Down Expand Up @@ -673,5 +678,6 @@ export function createForm<Type extends Record<string, unknown> = any>(
update,
subscribe,
getContext,
getSerializedState,
};
}
48 changes: 30 additions & 18 deletions packages/conform-react/context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,33 +51,41 @@ export interface FieldConfig<Type> extends BaseConfig<Type> {

export const Context = createContext<Record<string, Form>>({});

export function useFormContext(
formId: string,
localContext?: Form | undefined,
subjectRef?: MutableRefObject<SubscriptionSubject>,
): 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<SubscriptionSubject>,
): 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 }),
Expand All @@ -87,20 +95,24 @@ export function ConformBoundary(props: { context: Form; children: ReactNode }) {
return <Context.Provider value={value}>{props.children}</Context.Provider>;
}

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 (
<input
type="hidden"
name={STATE}
value={JSON.stringify({
key: context.state.key,
validated: context.state.validated,
})}
value={form.getSerializedState()}
form={props.formId}
/>
);
Expand Down
46 changes: 18 additions & 28 deletions packages/conform-react/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,35 +197,25 @@ export function useForm<Type extends Record<string, any>>(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;
},
},
}),
};
}

Expand Down
2 changes: 1 addition & 1 deletion packages/conform-react/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export {
export {
type Field,
type FieldConfig,
ConformBoundary,
FormContextProvider,
FormStateInput,
} from './context';
export { useForm, useFieldset, useFieldList, useField } from './hooks';
Expand Down
4 changes: 2 additions & 2 deletions playground/app/routes/simple-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,12 @@ export default function SimpleList() {
const items = useFieldList({
formId: form.id,
name: fields.items.name,
context: context,
context,
});

return (
<Form method="post" {...conform.form(form)}>
<FormStateInput formId={form.id} context={context} />
<FormStateInput context={context} />
<Playground title="Simple list" lastSubmission={lastResult}>
<Alert errors={fields.items.error} />
<ol>
Expand Down

0 comments on commit e5c87a8

Please sign in to comment.