Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: direct form reset instead of relying on DOM event #571

Merged
merged 2 commits into from
Apr 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 16 additions & 8 deletions packages/conform-dom/form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ export type Constraint = {
};

export type FormMeta<FormError> = {
formId: string;
isValueUpdated: boolean;
submissionStatus?: 'error' | 'success';
defaultValue: Record<string, unknown>;
Expand All @@ -125,7 +126,7 @@ export type FormMeta<FormError> = {

export type FormState<FormError> = Omit<
FormMeta<FormError>,
'isValueUpdated'
'formId' | 'isValueUpdated'
> & {
valid: Record<string, boolean>;
dirty: Record<string, boolean>;
Expand Down Expand Up @@ -193,6 +194,7 @@ export type SubscriptionSubject = {
| 'valid'
| 'dirty']?: SubscriptionScope;
} & {
formId?: boolean;
status?: boolean;
};

Expand All @@ -213,7 +215,7 @@ export type FormContext<
FormError = string[],
FormValue = Schema,
> = {
formId: string;
getFormId(): string;
submit(event: SubmitEvent): {
formData: FormData;
action: ReturnType<typeof getFormAction>;
Expand Down Expand Up @@ -263,6 +265,7 @@ function createFormMeta<Schema, FormError, FormValue>(
: {};
const initialValue = lastResult?.initialValue ?? defaultValue;
const result: FormMeta<FormError> = {
formId: options.formId,
isValueUpdated: false,
submissionStatus: lastResult?.status,
defaultValue,
Expand Down Expand Up @@ -693,7 +696,7 @@ export function createFormContext<
state = nextState;

const cache: Record<
Exclude<keyof SubscriptionSubject, 'status'>,
Exclude<keyof SubscriptionSubject, 'formId' | 'status'>,
Record<string, boolean>
> = {
value: {},
Expand All @@ -709,6 +712,7 @@ export function createFormContext<

if (
!subject ||
(subject.formId && prevMeta.formId !== nextMeta.formId) ||
(subject.status &&
prevState.submissionStatus !== nextState.submissionStatus) ||
shouldNotify(
Expand Down Expand Up @@ -884,6 +888,10 @@ export function createFormContext<
});
}

function reset() {
updateFormMeta(createFormMeta(latestOptions, true));
}

function onReset(event: Event) {
const element = getFormElement();

Expand All @@ -895,14 +903,14 @@ export function createFormContext<
return;
}

updateFormMeta(createFormMeta(latestOptions, true));
reset();
}

function report(result: SubmissionResult<FormError>) {
const formElement = getFormElement();

if (!result.initialValue) {
formElement?.reset();
reset();
return;
}

Expand Down Expand Up @@ -952,7 +960,7 @@ export function createFormContext<
Object.assign(latestOptions, options);

if (latestOptions.formId !== currentFormId) {
updateFormMeta(createFormMeta(latestOptions, true));
reset();
} else if (options.lastResult && options.lastResult !== currentResult) {
report(options.lastResult);
}
Expand Down Expand Up @@ -1061,8 +1069,8 @@ export function createFormContext<
}

return {
get formId() {
return latestOptions.formId;
getFormId() {
return meta.formId;
},
submit,
onReset,
Expand Down
39 changes: 30 additions & 9 deletions packages/conform-react/context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ export function useFormContext<Schema extends Record<string, any>, FormError>(
): FormContext<Schema, FormError, unknown> {
const contexts = useContext(Form);
const form = formId
? contexts.find((context) => context.formId === formId)
? contexts.find((context) => formId === context.getFormId())
: contexts[0];

if (!form) {
Expand Down Expand Up @@ -196,13 +196,23 @@ export function useSubjectRef(

export function updateSubjectRef(
ref: MutableRefObject<SubscriptionSubject>,
subject: 'status' | 'formId',
): void;
export function updateSubjectRef(
ref: MutableRefObject<SubscriptionSubject>,
subject: Exclude<keyof SubscriptionSubject, 'status' | 'formId'>,
scope: keyof SubscriptionScope,
name: string,
): void;
export function updateSubjectRef(
ref: MutableRefObject<SubscriptionSubject>,
subject: keyof SubscriptionSubject,
scope: keyof SubscriptionScope,
scope?: keyof SubscriptionScope,
name?: string,
): void {
if (subject === 'status') {
if (subject === 'status' || subject === 'formId') {
ref.current[subject] = true;
} else {
} else if (typeof scope !== 'undefined' && typeof name !== 'undefined') {
ref.current[subject] = {
...ref.current[subject],
[scope]: (ref.current[subject]?.[scope] ?? []).concat(name),
Expand All @@ -220,7 +230,7 @@ export function getMetadata<
stateSnapshot: FormState<FormError>,
name: FieldName<Schema, FormSchema, FormError> = '',
): Metadata<Schema, FormSchema, FormError> {
const id = name ? `${context.formId}-${name}` : context.formId;
const id = name ? `${context.getFormId()}-${name}` : context.getFormId();
const state = context.getState();

return new Proxy(
Expand Down Expand Up @@ -288,20 +298,25 @@ export function getMetadata<
// if the stateSnapshot is not the latest, then it must be accessed in a callback
if (state === stateSnapshot) {
switch (key) {
case 'id':
case 'errorId':
case 'descriptionId':
updateSubjectRef(subjectRef, 'formId');
break;
case 'key':
case 'initialValue':
case 'value':
case 'valid':
case 'dirty':
updateSubjectRef(subjectRef, name, key, 'name');
updateSubjectRef(subjectRef, key, 'name', name);
break;
case 'errors':
case 'allErrors':
updateSubjectRef(
subjectRef,
name,
'error',
key === 'errors' ? 'name' : 'prefix',
name,
);
break;
}
Expand Down Expand Up @@ -336,7 +351,10 @@ export function getFieldMetadata<

switch (key) {
case 'formId':
return context.formId;
if (state === stateSnapshot) {
updateSubjectRef(subjectRef, 'formId');
}
return context.getFormId();
case 'required':
case 'minLength':
case 'maxLength':
Expand All @@ -351,7 +369,7 @@ export function getFieldMetadata<
const initialValue = state.initialValue[name] ?? [];

if (state === stateSnapshot) {
updateSubjectRef(subjectRef, name, 'initialValue', 'name');
updateSubjectRef(subjectRef, 'initialValue', 'name', name);
}

if (!Array.isArray(initialValue)) {
Expand Down Expand Up @@ -401,6 +419,9 @@ export function getFormMetadata<
[wrappedSymbol]: context,
};
case 'status':
if (state === stateSnapshot) {
updateSubjectRef(subjectRef, 'status');
}
return state.submissionStatus;
case 'validate':
case 'update':
Expand Down
22 changes: 18 additions & 4 deletions playground/app/routes/subscription.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,16 +47,19 @@ export async function action({ request }: ActionArgs) {
schema,
});

return json(submission.reply());
return json({
result: submission.reply(),
submitted: submission.status === 'success',
});
}

export default function Example() {
const { noClientValidate, isStrcitMode: strict } =
useLoaderData<typeof loader>();
const lastResult = useActionData<typeof action>();
const actionData = useActionData<typeof action>();
const [form, fields] = useForm({
id: 'example',
lastResult,
id: `example-${actionData?.submitted ? 'submitted' : 'initial'}`,
lastResult: actionData?.result,
onValidate: !noClientValidate
? ({ formData }) => parseWithZod(formData, { schema })
: undefined,
Expand All @@ -71,19 +74,30 @@ export default function Example() {
const message = fields.message.name;
const description = (
<ul className="space-y-1 list-disc">
<FormMetadata strict={strict} subject="id" />
<FormMetadata strict={strict} subject="errorId" />
<FormMetadata strict={strict} subject="descriptionId" />
<FormMetadata strict={strict} subject="initialValue" />
<FormMetadata strict={strict} subject="value" />
<FormMetadata strict={strict} subject="key" />
<FormMetadata strict={strict} subject="dirty" />
<FormMetadata strict={strict} subject="valid" />
<FormMetadata strict={strict} subject="errors" />
<FormMetadata strict={strict} subject="allErrors" />
<FieldMetadata strict={strict} name={name} subject="id" />
<FieldMetadata strict={strict} name={name} subject="formId" />
<FieldMetadata strict={strict} name={name} subject="errorId" />
<FieldMetadata strict={strict} name={name} subject="descriptionId" />
<FieldMetadata strict={strict} name={name} subject="initialValue" />
<FieldMetadata strict={strict} name={name} subject="value" />
<FieldMetadata strict={strict} name={name} subject="key" />
<FieldMetadata strict={strict} name={name} subject="dirty" />
<FieldMetadata strict={strict} name={name} subject="valid" />
<FieldMetadata strict={strict} name={name} subject="errors" />
<FieldMetadata strict={strict} name={message} subject="id" />
<FieldMetadata strict={strict} name={message} subject="formId" />
<FieldMetadata strict={strict} name={message} subject="errorId" />
<FieldMetadata strict={strict} name={message} subject="descriptionId" />
<FieldMetadata strict={strict} name={message} subject="initialValue" />
<FieldMetadata strict={strict} name={message} subject="value" />
<FieldMetadata strict={strict} name={message} subject="key" />
Expand Down
Loading
Loading