React Router adapter

Server-side validation

One of the core features of this library is that you can re-use your validation on the server. Let's use this validator as an example:

const validator = withZod(
  z.object({
    firstName: z
      .string()
      .min(1, { message: "First name is required" }),
    lastName: z
      .string()
      .min(1, { message: "Last name is required" }),
    email: z
      .string()
      .min(1, { message: "Email is required" })
      .email("Must be a valid email"),
  }),
);

All you need to do is call validator.validate on the form data returned from request.formData.

export const action = async ({
  request,
}: DataFunctionArgs) => {
  const result = await validator.validate(
    await request.formData(),
  );

  if (result.error) {
    // validationError comes from `@rvf/react-router`
    return validationError(
      result.error,
      result.submittedData,
    );
  }

  const { firstName, lastName, email } = result.data;
  // Do something with the data
};

When we use the validationError to return our validation errors, the useForm or ValidatedForm in our route component will automatically pick up those errors. If you also pass result.submittedData to as the second argument to validationError, it will automatically repopulate the form with the data submitted by the user.

Caveats

If you have more than one form active on the page, you'll need to take some extra steps to ensure you don't show errors on the wrong form.

const form = useForm({
  validator,
  id: "form-1",
})

return (
  <form {...form.getFormProps()}>
    {/* You don't need this part if you're using
      `ValidatedForm` instead of `useForm` */}
    {form.renderFormIdInput()}

    {/* The rest of the form */}
  </form>
)

Example

Try disabling JavaScript in your browser to see how server validation errors are handled.

Server validation with React Router

Create an account

API differences

The API of useForm and ValidatedForm are almost identical to the base @rvf/react API. The main difference is that useForm can accept many of the props you would normally pass to Form or useSubmit from Remix. In order for these to work correctly, you should pass them to the useForm hook instead of the Form component.

form or Form?

Normally, with Remix, you would use the Form component for most of your forms. When using RVF though, you don't need to do that. useForm calls preventDefault on the form's submit callback, to take control of the form submission.