#Error handling without Loosing State?

1 messages · Page 1 of 1 (latest)

zinc karma
#

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>
  )
}
#

I want to display the two different errors on the Page without clearing the email field. And I want to clear it and show a success message, if the email was sent.

To get alle the types correct it looks a bit weird.. I'm aware of the error boundary, but if I would use it, I need to extract nearly the whole markup to a external component, which would be kind of okay I think, but the email field/state will be gone every time.

Am I missing something? I asume the ErrorBoundary and throwing the error would be the "remix way" but loosing state and duplicate the whole markup seems kind of wrong. (And this is just a minimal example with one input and very stripped down markup)

loud belfry
#

I normally just return the form data in the action if there's an error and use the defaultValue on the input to display this

zinc karma
#

With ErrorBoundary and the markup extracted to a component?

lethal saddle
#

use the fetcher?

loud belfry
#

If it's a known error, other than a validation issue, I normally just return a toast from the action rather than triggering an ErrorBondary.

zinc karma
#

I think a fetcher is not the solution for my problem. Neither is returning a toast (it's more like a workaround).. I think the problem is mainly in typescript and it's discrimanted union handling...