0
0
RemixDebug / FixBeginner · 4 min read

How to Handle Form Errors in Remix: Simple Fixes and Best Practices

In Remix, handle form errors by returning error data from your action function and reading it in your component using useActionData(). This lets you show validation messages or other errors directly on the form after submission.
🔍

Why This Happens

When you submit a form in Remix, if you don't return error information from your action function, the form will not show any error messages. This happens because Remix expects you to send back data that your component can use to display errors. Without this, users won't know what went wrong.

javascript
export const action = async ({ request }) => {
  const formData = await request.formData();
  const email = formData.get('email');

  if (!email) {
    // Missing return of error data
    return { errors: { email: 'Email is required' } };
  }

  // Process form normally
  return null;
};

export default function ContactForm() {
  // No useActionData used to get errors
  return (
    <form method="post">
      <input type="email" name="email" placeholder="Email" />
      <button type="submit">Send</button>
    </form>
  );
}
Output
Form submits but no error message is shown when email is missing.
🔧

The Fix

Return an object with error messages from the action function when validation fails. Then use useActionData() in your component to read these errors and display them near the form fields. This way, users see exactly what needs fixing.

javascript
import { useActionData } from '@remix-run/react';

export const action = async ({ request }) => {
  const formData = await request.formData();
  const email = formData.get('email');

  if (!email) {
    return { errors: { email: 'Email is required' } };
  }

  // Process form normally
  return null;
};

export default function ContactForm() {
  const actionData = useActionData();

  return (
    <form method="post" noValidate>
      <input
        type="email"
        name="email"
        placeholder="Email"
        aria-invalid={actionData?.errors?.email ? true : undefined}
        aria-describedby={actionData?.errors?.email ? 'email-error' : undefined}
      />
      {actionData?.errors?.email && (
        <p id="email-error" style={{ color: 'red' }} role="alert">
          {actionData.errors.email}
        </p>
      )}
      <button type="submit">Send</button>
    </form>
  );
}
Output
When submitting without email, the form shows a red error message: "Email is required" below the input.
🛡️

Prevention

Always validate form data in your action function and return clear error objects. Use useActionData() to display these errors in your form. Add aria-invalid and aria-describedby attributes for accessibility. Use noValidate on the form to disable browser default validation if you want full control.

Consider using schema validation libraries like Zod or Yup to keep validation consistent and clean. Also, test your forms by submitting invalid data to confirm errors show correctly.

⚠️

Related Errors

Common related errors include:

  • Not using useActionData() to read errors, so messages never appear.
  • Returning plain strings instead of objects, causing rendering issues.
  • Forgetting to add accessibility attributes, making errors hard to notice for screen readers.
  • Relying only on client-side validation, which can be bypassed.

Fix these by following Remix patterns for server-side validation and error display.

Key Takeaways

Return error objects from your Remix action function to signal form errors.
Use useActionData() in your component to read and display these errors.
Add accessibility attributes like aria-invalid and aria-describedby for better UX.
Validate on the server side to ensure reliable error handling.
Use schema validation libraries to keep validation clean and consistent.