Skip to content

Commit

Permalink
feat: submission.reply() api
Browse files Browse the repository at this point in the history
  • Loading branch information
edmundhung committed Dec 12, 2023
1 parent e1523cc commit f6001db
Show file tree
Hide file tree
Showing 25 changed files with 187 additions and 341 deletions.
12 changes: 6 additions & 6 deletions examples/nextjs/app/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ export async function login(prevState: unknown, formData: FormData) {
schema: loginSchema,
});

if (!submission.value) {
return submission.reject();
if (submission.status !== 'success') {
return submission.reply();
}

redirect(`/?value=${JSON.stringify(submission.value)}`);
Expand All @@ -21,8 +21,8 @@ export async function createTodos(prevState: unknown, formData: FormData) {
schema: todosSchema,
});

if (!submission.value) {
return submission.reject();
if (submission.status !== 'success') {
return submission.reply();
}

redirect(`/?value=${JSON.stringify(submission.value)}`);
Expand All @@ -44,8 +44,8 @@ export async function signup(prevState: unknown, formData: FormData) {
async: true,
});

if (!submission.value) {
return submission.reject();
if (submission.status !== 'success') {
return submission.reply();
}

redirect(`/?value=${JSON.stringify(submission.value)}`);
Expand Down
4 changes: 2 additions & 2 deletions examples/react-router/src/login-fetcher.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ export async function action({ request }: ActionFunctionArgs) {
const formData = await request.formData();
const submission = parseWithZod(formData, { schema });

if (!submission.value) {
return json(submission.reject());
if (submission.status !== 'success') {
return json(submission.reply());
}

return redirect(`/?value=${JSON.stringify(submission.value)}`);
Expand Down
4 changes: 2 additions & 2 deletions examples/react-router/src/login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ export async function action({ request }: ActionFunctionArgs) {
const formData = await request.formData();
const submission = parseWithZod(formData, { schema });

if (!submission.value) {
return json(submission.reject());
if (submission.status !== 'success') {
return json(submission.reply());
}

return redirect(`/?value=${JSON.stringify(submission.value)}`);
Expand Down
4 changes: 2 additions & 2 deletions examples/react-router/src/signup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@ export async function action({ request }: ActionFunctionArgs) {
async: true,
});

if (!submission.value) {
return json(submission.reject());
if (submission.status !== 'success') {
return json(submission.reply());
}

return redirect(`/?value=${JSON.stringify(submission.value)}`);
Expand Down
4 changes: 2 additions & 2 deletions examples/react-router/src/todos.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ export async function action({ request }: ActionFunctionArgs) {
schema: todosSchema,
});

if (!submission.value) {
return json(submission.reject());
if (submission.status !== 'success') {
return json(submission.reply());
}

return redirect(`/?value=${JSON.stringify(submission.value)}`);
Expand Down
4 changes: 2 additions & 2 deletions examples/remix/app/routes/login-fetcher.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ export async function action({ request }: ActionArgs) {
const formData = await request.formData();
const submission = parseWithZod(formData, { schema });

if (!submission.value) {
return json(submission.reject());
if (submission.status !== 'success') {
return json(submission.reply());
}

return redirect(`/?value=${JSON.stringify(submission.value)}`);
Expand Down
4 changes: 2 additions & 2 deletions examples/remix/app/routes/login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ export async function action({ request }: ActionArgs) {
const formData = await request.formData();
const submission = parseWithZod(formData, { schema });

if (!submission.value) {
return json(submission.reject());
if (submission.status !== 'success') {
return json(submission.reply());
}

return redirect(`/?value=${JSON.stringify(submission.value)}`);
Expand Down
4 changes: 2 additions & 2 deletions examples/remix/app/routes/signup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ export async function action({ request }: ActionArgs) {
async: true,
});

if (!submission.value) {
return json(submission.reject());
if (submission.status !== 'success') {
return json(submission.reply());
}

return redirect(`/?value=${JSON.stringify(submission.value)}`);
Expand Down
4 changes: 2 additions & 2 deletions examples/remix/app/routes/todos.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ export async function action({ request }: ActionArgs) {
schema: todosSchema,
});

if (!submission.value) {
return json(submission.reject());
if (submission.status !== 'success') {
return json(submission.reply());
}

return redirect(`/?value=${JSON.stringify(submission.value)}`);
Expand Down
4 changes: 2 additions & 2 deletions packages/conform-dom/form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -654,8 +654,8 @@ export function createFormContext<
submitter,
});

if (!submission.value && submission.error !== null) {
report(submission.reject());
if (submission.status !== 'success' && submission.error !== null) {
report(submission.reply());
event.preventDefault();
}

Expand Down
143 changes: 54 additions & 89 deletions packages/conform-dom/submission.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,27 +18,23 @@ export type SubmissionContext<Value = null, Error = unknown> = {
intent: Intent | null;
payload: Record<string, unknown>;
fields: string[];
value: Value | null;
error: Record<string, Error | null> | null;
value?: Value;
error?: Record<string, Error | null> | null;
state: SubmissionState;
};

export type Submission<Schema, Error = unknown, Value = Schema> =
| {
type: 'submit';
status: 'success';
payload: Record<string, unknown>;
value: Value | null;
error: Record<string, Error | null> | null;
reject(options?: RejectOptions<Error>): SubmissionResult<Error>;
accept(options?: AcceptOptions): SubmissionResult<Error>;
value: Value;
reply(options?: ReplyOptions<Error>): SubmissionResult<Error>;
}
| {
type: 'update';
status: 'error' | undefined;
payload: Record<string, unknown>;
value: null;
error: Record<string, Error | null> | null;
reject(options?: RejectOptions<Error>): SubmissionResult<Error>;
accept(options?: AcceptOptions): SubmissionResult<Error>;
reply(options?: ReplyOptions<Error>): SubmissionResult<Error>;
};

export type SubmissionResult<Error = unknown> = {
Expand All @@ -49,20 +45,16 @@ export type SubmissionResult<Error = unknown> = {
state?: SubmissionState;
};

export type AcceptOptions =
export type ReplyOptions<Error> =
| {
resetForm?: boolean;
}
| {
formErrors?: Error;
fieldErrors?: Record<string, Error>;
hideFields?: string[];
};

export type RejectOptions<Error> = {
formError?: Error;
fieldError?: Record<string, Error>;
hideFields?: string[];
};

/**
* The name to be used when submitting an intent
*/
Expand Down Expand Up @@ -109,8 +101,6 @@ export function getSubmissionContext(
intent: getIntent(intent),
state: state ? JSON.parse(state) : { validated: {} },
fields,
value: null,
error: null,
};
}

Expand Down Expand Up @@ -248,8 +238,8 @@ export function parse<Value, Error>(

return createSubmission({
...context,
value: resolved.value ?? null,
error,
value: resolved.value,
error: resolved.error,
});
};

Expand All @@ -261,74 +251,32 @@ export function parse<Value, Error>(
}

export function createSubmission<Value, Error>(
context: Required<SubmissionContext<Value, Error>>,
context: SubmissionContext<Value, Error>,
): Submission<Value, Error> {
if (context.intent) {
if (context.intent || !context.value || context.error) {
return {
type: 'update',
status: !context.intent ? 'error' : undefined,
payload: context.payload,
value: null,
error: context.error,
accept(options) {
return acceptSubmission(context, options);
},
reject(options) {
return rejectSubmission(context, options);
error: typeof context.error !== 'undefined' ? context.error : {},
reply(options) {
return replySubmission(context, options);
},
};
}

return {
type: 'submit',
status: 'success',
payload: context.payload,
value: context.value,
error: context.error,
accept(options) {
return acceptSubmission(context, options);
},
reject(options) {
return rejectSubmission(context, options);
reply(options) {
return replySubmission(context, options);
},
};
}

export function hideFields(
payload: Record<string, unknown>,
fields: string[],
): void {
for (const name of fields) {
const value = getValue(payload, name);

if (typeof value !== 'undefined') {
setValue(payload, name, () => undefined);
}
}
}

export function acceptSubmission<Error>(
context: Required<SubmissionContext<unknown, Error>>,
options?: AcceptOptions,
): SubmissionResult<Error> {
if (options) {
if ('resetForm' in options && options.resetForm) {
return { status: 'success', initialValue: null };
}

if ('hideFields' in options && options.hideFields) {
hideFields(context.payload, options.hideFields);
}
}
return {
status: 'success',
initialValue: simplify(context.payload) ?? {},
error: simplify(context.error),
state: context.state,
};
}

export function rejectSubmission<Error>(
context: Required<SubmissionContext<unknown, Error>>,
options?: RejectOptions<Error>,
export function replySubmission<Error>(
context: SubmissionContext<unknown, Error>,
options: ReplyOptions<Error> = {},
): SubmissionResult<Error> {
switch (context.intent?.type) {
case 'reset': {
Expand All @@ -342,28 +290,45 @@ export function rejectSubmission<Error>(
}
}

const error = Object.entries(context.error ?? {}).reduce<
if ('resetForm' in options && options.resetForm) {
return { initialValue: null };
}

if ('hideFields' in options && options.hideFields) {
for (const name of options.hideFields) {
const value = getValue(context.payload, name);

if (typeof value !== 'undefined') {
setValue(context.payload, name, () => undefined);
}
}
}

const submissionError = Object.entries(context.error ?? {}).reduce<
Record<string, Error | null>
>((result, [name, currentError]) => {
>((result, [name, error]) => {
if (context.state.validated[name]) {
const newError =
name === '' ? options?.formError : options?.fieldError?.[name];

result[name] = newError ?? currentError;
result[name] = error;
}

return result;
}, {});

if (options?.hideFields) {
hideFields(context.payload, options.hideFields);
}
const extraError =
'formErrors' in options || 'fieldErrors' in options
? simplify({
'': options.formErrors,
...options.fieldErrors,
})
: null;
const error = simplify({
...submissionError,
...extraError,
});

return {
status: context.intent !== null ? undefined : 'error',
intent: context.intent !== null ? context.intent : undefined,
status: context.intent ? undefined : error ? 'error' : 'success',
initialValue: simplify(context.payload) ?? {},
error: simplify(error),
error,
state: context.state,
};
}
Expand Down
4 changes: 2 additions & 2 deletions packages/conform-zod/parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,14 +116,14 @@ export function parseWithZod<Schema extends ZodTypeAny, Error>(
result: SafeParseReturnType<Input, Output>,
) => {
return {
value: result.success ? result.data : null,
value: result.success ? result.data : undefined,
error: !result.success
? getError<Error | string[]>(
result.error,
options.formatError ??
((issues) => issues.map((issue) => issue.message)),
)
: {},
: undefined,
};
};

Expand Down
6 changes: 1 addition & 5 deletions playground/app/routes/_index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,7 @@ export async function action({ request }: ActionArgs) {
const formData = await request.formData();
const submission = parseWithZod(formData, { schema });

if (!submission.value) {
return json(submission.reject());
}

return json(submission.accept());
return json(submission.reply());
}

export default function Example() {
Expand Down
6 changes: 1 addition & 5 deletions playground/app/routes/async-validation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,7 @@ export async function action({ request }: ActionArgs) {
async: true,
});

if (!submission.value) {
return json(submission.reject());
}

return json(submission.accept());
return json(submission.reply());
}

export default function EmployeeForm() {
Expand Down
Loading

0 comments on commit f6001db

Please sign in to comment.