Skip to content

Commit

Permalink
feat(conform-react): report helper (#222)
Browse files Browse the repository at this point in the history
  • Loading branch information
edmundhung authored Jul 19, 2023
1 parent b7e0cce commit c0843a3
Show file tree
Hide file tree
Showing 40 changed files with 132 additions and 213 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ A progressive enhancement first form validation library for Remix and React Rout
Here is an example built with Remix:

```tsx
import { useForm } from '@conform-to/react';
import { useForm, report } from '@conform-to/react';
import { parse } from '@conform-to/zod';
import { Form } from '@remix-run/react';
import { json } from '@remix-run/node';
Expand All @@ -34,7 +34,7 @@ export async function action({ request }: ActionArgs) {
const submission = parse(formData, { schema });

if (!submission.value || submission.intent !== 'submit') {
return json(submission, { status: 400 });
return json(report(submission));
}

return await authenticate(submission.value);
Expand Down
22 changes: 5 additions & 17 deletions docs/tutorial.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ export default function LoginForm() {
Now, it's time to enhance the login form using Conform.

```tsx
import { parse, useForm } from '@conform-to/react';
import { parse, report, useForm } from '@conform-to/react';
import { type ActionArgs, json } from '@remix-run/node';
import { Form, useActionData } from '@remix-run/react';
import { authenticate } from '~/auth';
Expand Down Expand Up @@ -123,18 +123,11 @@ export async function action({ request }: ActionArgs) {
},
});

// Send the submission data back to client
// Report the submission to client
// 1) if the intent is not `submit`, or
// 2) if there is any error
if (submission.intent !== 'submit' || !submission.value) {
return json({
...submission,
// The payload will be used as the default value
// if the document is reloaded on form submit
payload: {
email: submission.payload.email,
},
});
return json(report(submission));
}

return await authenticate(submission.value.email, submission.value.password);
Expand Down Expand Up @@ -180,7 +173,7 @@ Conform will trigger a [server validation](./validation.md#server-validation) to
Server validation might some time be too slow to provide a good user experience. We can also reuse the validation logic on the client for a instant feedback.

```tsx
import { parse, useForm } from '@conform-to/react';
import { parse, report, useForm } from '@conform-to/react';
import { type ActionArgs, json } from '@remix-run/node';
import { Form, useActionData } from '@remix-run/react';
import { authenticate } from '~/auth';
Expand Down Expand Up @@ -218,12 +211,7 @@ export async function action({ request }: ActionArgs) {
const submission = parseLoginForm(formData);

if (submission.intent !== 'submit' || !submission.value) {
return json({
...submission,
payload: {
email: submission.payload.email,
},
});
return json(report(submission));
}

return await authenticate(submission.value.email, submission.value.password);
Expand Down
23 changes: 9 additions & 14 deletions docs/validation.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Conform supports several validation modes. In this section, we will walk you thr
**Conform** enables you to validate a form **fully server side**.

```tsx
import { useForm, parse } from '@conform-to/react';
import { useForm, parse, report } from '@conform-to/react';

export async function action({ request }: ActionArgs) {
const formData = await request.formData();
Expand Down Expand Up @@ -54,23 +54,17 @@ export async function action({ request }: ActionArgs) {
});

if (!submission.value || submission.intent !== 'submit') {
return json(submission);
return json(report(submission));
}

const user = await signup(submission.payload);

if (!user) {
return json({
...submission,
/**
* By specifying the error path as '' (root), the message will be
* treated as a form-level error and populated
* on the client side as `form.error`
*/
error: {
'': 'Oops! Something went wrong.',
},
});
return json(
report(submission, {
formError: ['Oops! Something went wrong.'],
}),
);
}

return redirect('/');
Expand All @@ -94,6 +88,7 @@ You can also validate the form with a schema validation library like [yup](https

```tsx
// Import the parse helper from @conform-to/zod instead
import { report } from '@conform-to/react';
import { parse } from '@conform-to/zod';
import { z } from 'zod';

Expand All @@ -113,7 +108,7 @@ export async function action({ request }: ActionArgs) {
});

if (!submission.value || submission.intent !== 'submit') {
return json(submission);
return json(report(submission));
}

return await signup(data);
Expand Down
12 changes: 6 additions & 6 deletions examples/react-router/src/login-fetcher.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Submission } from '@conform-to/react';
import { useForm, parse, validateConstraint } from '@conform-to/react';
import { useForm, parse, report, validateConstraint } from '@conform-to/react';
import type { ActionFunctionArgs } from 'react-router-dom';
import { useFetcher, json, redirect } from 'react-router-dom';

Expand All @@ -25,11 +25,11 @@ export async function action({ request }: ActionFunctionArgs) {
submission.payload.password,
))
) {
return json({
...submission,
// '' denote the root which is treated as form error
error: { '': 'Invalid credential' },
});
return json(
report(submission, {
formError: ['Invalid credential'],
}),
);
}

return redirect('/');
Expand Down
8 changes: 2 additions & 6 deletions examples/react-router/src/login.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Submission } from '@conform-to/react';
import { useForm, parse, validateConstraint } from '@conform-to/react';
import { useForm, parse, validateConstraint, report } from '@conform-to/react';
import type { ActionFunctionArgs } from 'react-router-dom';
import { Form, useActionData, json, redirect } from 'react-router-dom';

Expand All @@ -25,11 +25,7 @@ export async function action({ request }: ActionFunctionArgs) {
submission.payload.password,
))
) {
return json({
...submission,
// '' denote the root which is treated as form error
error: { '': 'Invalid credential' },
});
return json(report(submission, { formError: ['Invalid credential'] }));
}

return redirect('/');
Expand Down
9 changes: 2 additions & 7 deletions examples/react-router/src/signup.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Submission } from '@conform-to/react';
import { useForm } from '@conform-to/react';
import { useForm, report } from '@conform-to/react';
import { parse, refine } from '@conform-to/zod';
import type { ActionFunctionArgs } from 'react-router-dom';
import { Form, useActionData, json } from 'react-router-dom';
Expand Down Expand Up @@ -57,12 +57,7 @@ export async function action({ request }: ActionFunctionArgs) {
});

if (!submission.value || submission.intent !== 'submit') {
return json({
...submission,
payload: {
username: submission.payload.username,
},
});
return json(report(submission));
}

throw new Error('Not implemented');
Expand Down
10 changes: 8 additions & 2 deletions examples/react-router/src/todos.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import type { FieldsetConfig } from '@conform-to/react';
import { useForm, useFieldset, useFieldList, list } from '@conform-to/react';
import {
useForm,
useFieldset,
useFieldList,
report,
list,
} from '@conform-to/react';
import { parse } from '@conform-to/zod';
import type { ActionFunctionArgs } from 'react-router-dom';
import { Form, useActionData, json } from 'react-router-dom';
Expand All @@ -23,7 +29,7 @@ export async function action({ request }: ActionFunctionArgs) {
});

if (!submission.value || submission.intent !== 'submit') {
return json(submission);
return json(report(submission));
}

throw new Error('Not implemented');
Expand Down
10 changes: 2 additions & 8 deletions examples/remix/app/routes/login-fetcher.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { conform, parse, useForm } from '@conform-to/react';
import { conform, parse, report, useForm } from '@conform-to/react';
import type { ActionArgs } from '@remix-run/node';
import { json } from '@remix-run/node';
import { useFetcher } from '@remix-run/react';
Expand Down Expand Up @@ -55,13 +55,7 @@ export async function action({ request }: ActionArgs) {
*/
if (!submission.value || submission.intent !== 'submit') {
// Always sends the submission state back to client until the user is signed up
return json({
...submission,
payload: {
// Never send the password back to client
email: submission.payload.email,
},
});
return json(report(submission));
}

throw new Error('Not implemented');
Expand Down
10 changes: 2 additions & 8 deletions examples/remix/app/routes/login.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { conform, parse, useForm } from '@conform-to/react';
import { conform, parse, report, useForm } from '@conform-to/react';
import type { ActionArgs } from '@remix-run/node';
import { json } from '@remix-run/node';
import { Form, useActionData } from '@remix-run/react';
Expand Down Expand Up @@ -55,13 +55,7 @@ export async function action({ request }: ActionArgs) {
*/
if (!submission.value || submission.intent !== 'submit') {
// Always sends the submission state back to client until the user is signed up
return json({
...submission,
payload: {
// Never send the password back to client
email: submission.payload.email,
},
});
return json(report(submission));
}

throw new Error('Not implemented');
Expand Down
9 changes: 2 additions & 7 deletions examples/remix/app/routes/signup.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { conform, useForm } from '@conform-to/react';
import { conform, report, useForm } from '@conform-to/react';
import { parse, refine } from '@conform-to/zod';
import type { ActionArgs } from '@remix-run/node';
import { json } from '@remix-run/node';
Expand Down Expand Up @@ -57,12 +57,7 @@ export async function action({ request }: ActionArgs) {
});

if (!submission.value || submission.intent !== 'submit') {
return json({
...submission,
payload: {
username: submission.payload.username,
},
});
return json(report(submission));
}

throw new Error('Not implemented');
Expand Down
3 changes: 2 additions & 1 deletion examples/remix/app/routes/todos.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
useFieldset,
useFieldList,
conform,
report,
list,
} from '@conform-to/react';
import { parse } from '@conform-to/zod';
Expand All @@ -30,7 +31,7 @@ export async function action({ request }: ActionArgs) {
});

if (!submission.value || submission.intent !== 'submit') {
return json(submission);
return json(report(submission));
}

throw new Error('Not implemented');
Expand Down
9 changes: 2 additions & 7 deletions examples/yup/app/routes/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { conform, useForm } from '@conform-to/react';
import { conform, report, useForm } from '@conform-to/react';
import { parse } from '@conform-to/yup';
import type { ActionArgs } from '@remix-run/node';
import { json } from '@remix-run/node';
Expand All @@ -25,12 +25,7 @@ export async function action({ request }: ActionArgs) {
const submission = parse(formData, { schema });

if (!submission.value || submission.intent !== 'submit') {
return json({
...submission,
payload: {
email: submission.payload.email,
},
});
return json(report(submission));
}

throw new Error('Not implemented');
Expand Down
7 changes: 1 addition & 6 deletions examples/zod/app/routes/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,7 @@ export async function action({ request }: ActionArgs) {
const submission = parse(formData, { schema });

if (!submission.value || submission.intent !== 'submit') {
return json({
...submission,
payload: {
email: submission.payload.email,
},
});
return json(report(submission));
}

throw new Error('Not implemented');
Expand Down
21 changes: 21 additions & 0 deletions packages/conform-react/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,27 @@ type SubmissionResult = {
error: Submission['error'];
};

interface ReportOptions {
formError?: string[];
resetForm?: boolean;
}

export function report(
submission: Submission,
options?: ReportOptions,
): SubmissionResult {
return {
intent: submission.intent,
payload: options?.resetForm ? null : submission.payload,
error: options?.formError
? {
...submission.error,
'': options.formError.concat(submission.error[''] ?? []),
}
: submission.error,
};
}

export interface FormConfig<
Output extends Record<string, any>,
Input extends Record<string, any> = Output,
Expand Down
1 change: 1 addition & 0 deletions packages/conform-react/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ export {
useFieldList,
useInputEvent,
validateConstraint,
report,
} from './hooks.js';
export * as conform from './helpers.js';
4 changes: 2 additions & 2 deletions packages/conform-yup/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ function ExampleForm() {
Or when parsing the formData on server side (e.g. Remix):

```tsx
import { useForm } from '@conform-to/react';
import { useForm, report } from '@conform-to/react';
import { parse } from '@conform-to/yup';
import * as yup from 'yup';

Expand All @@ -84,7 +84,7 @@ export async function action({ request }) {
});

if (!submission.value || submission.intent !== 'submit') {
return submission;
return report(submission);
}

// ...
Expand Down
4 changes: 2 additions & 2 deletions packages/conform-zod/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ function ExampleForm() {
Or when parsing the formData on server side (e.g. Remix):

```tsx
import { useForm } from '@conform-to/react';
import { useForm, report } from '@conform-to/react';
import { parse } from '@conform-to/zod';
import { z } from 'zod';

Expand All @@ -86,7 +86,7 @@ export async function action({ request }) {
});

if (!submission.value || submission.intent !== 'submit') {
return submission;
return report(submission);
}

// ...
Expand Down
Loading

0 comments on commit c0843a3

Please sign in to comment.