Hi all,
I'm just learning remix and was wondering how to handle errors correctly without loosing state. Given the following minimal "get password reset link" example:
const forgotPasswordSchema = z.object({
email: z.string(),
})
export const action = async ({ request }: ActionFunctionArgs) => {
const body = Object.fromEntries(await request.formData())
const result = forgotPasswordSchema.safeParse(body)
if (!result.success) {
return json({
success: false as const,
errorType: 'verificationError' as const,
errors: result.error.flatten().fieldErrors,
})
}
try {
await myDatabase.requestPasswordReset(result.data.email)
} catch (e) {
return json({
success: false as const,
errorType: 'databaseError' as const,
})
}
return json({
success: true as const,
})
}
export default function Page() {
const actionData = useActionData<typeof action>()
const $form = useRef<HTMLFormElement>(null)
const $email = useRef<HTMLInputElement>(null)
let navigation = useNavigation()
useEffect(
function resetFormOnError() {
if (navigation.state === 'idle' && actionData?.success) {
$form.current?.reset()
$email.current?.blur()
}
},
[navigation.state, actionData],
)
const fieldErrors =
actionData?.success === false &&
actionData.errorType === 'verificationError'
? actionData.errors
: {}
const generalError =
actionData?.success === false && actionData.errorType === 'databaseError'
return (
<div>
{generalError && <div className="error-alert">Oops.</div>}
{actionData?.success && <div className="success-alert">Yay!</div>}
<form method="POST" ref={$form}>
<label htmlFor="email">Email</label>
<input type="email" name="email" ref={$email} />
{fieldErrors.email && (
<div className="error-alert">{fieldErrors.email}</div>
)}
</form>
</div>
)
}