-
I have this Login component (simplified): import { conform, useForm } from '@conform-to/react'
import { parse } from '@conform-to/zod'
import { type ActionFunctionArgs, Form, json, redirect, useActionData } from 'react-router-dom'
import { z } from 'zod'
import { graphql } from '../__generated__/gql'
import { mutation } from '../utils/mutation'
const loginMutation = graphql(`
mutation Login($email: String!, $password: String!) {
login(email: $email, password: $password) {
token
}
}
`)
const schema = z.object({
email: z
.string({ required_error: 'Email is required' })
.email('Email is invalid'),
password: z.string({ required_error: 'Password is required' }),
})
export async function action({ request }: ActionFunctionArgs) {
const formData = await request.formData()
const submission = parse(formData, { schema })
if (!submission.value) {
return json(submission)
}
try {
const data = await mutation(loginMutation, submission.value)
sessionStorage.setItem('token', data.login.token)
} catch (error) {
return null
}
return redirect('/')
}
export const Component = function Login() {
const lastSubmission = useActionData() as any
const [form, { email, password }] = useForm({
lastSubmission,
onValidate({ formData }) {
return parse(formData, { schema })
},
shouldRevalidate: 'onBlur',
})
return (
<Form method="post" {...form.props}>
<h2>Login</h2>
<fieldset>
<label>
Email{' '}
<input
className={email.error ? 'error' : ''}
{...conform.input(email)}
/>
<div>{email.error}</div>
</label>
</fieldset>
<fieldset>
<label>
Password{' '}
<input
className={password.error ? 'error' : ''}
{...conform.input(password, { type: 'password' })}
/>
<div>{password.error}</div>
</label>
</fieldset>
<button>Login</button>
</Form>
)
}
export const ErrorBoundary = Component When the mutation fetch request fails (try/catch block in One way I've managed to do this is by exporting the component as I'm now wondering if there is a way of doing this that doesn't involve re-rendering using the ErrorBoundary and accessing |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 1 reply
-
Conform maps error to each input by the path and treat the root (empty string) path as the form error. For example, you can use .transform() from zod like this: export async function action({ request }: ActionFunctionArgs) {
const formData = await request.formData()
const submission = parse(formData, {
schema: schema.tranform(async (value, ctx) => {
try {
return await mutation(loginMutation, submission.value)
} catch (error) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: 'Your form error message',
// You can set a path for the issue but it's default to the root already
});
// This makes `submission.value` falsy and so being returned to the client with the condition below (i.e. !submission.value)
return null;
}
})
})
if (!submission.value) {
return json(submission)
}
sessionStorage.setItem('token', submission.value.login.token)
return redirect('/')
} Or, assigning the error manually like this: try {
// ...
} catch (error) {
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: {
'': 'Your form error message',
},
});
} Now, you can access the message through export const Component = function Login() {
const lastSubmission = useActionData() as any
const [form, { email, password }] = useForm({
lastSubmission,
onValidate({ formData }) {
return parse(formData, { schema })
},
shouldRevalidate: 'onBlur',
})
console.log(form.errors);
// ...
} |
Beta Was this translation helpful? Give feedback.
Thanks a lot, @edmundhung!
Both examples didn't work like that exactly, but gave me enough hints to make them work
Example 1: Enable
async
and usingvalue
instead ofsubmission.value
: