How toValidate Form in Remix with clientAction

Imagine you're building a form like this one

<form method="post">
  <input name="title" />
  <input name="slug" />
  <textarea name="excerpt" />
  <textarea name="content" /> 
</form>

Server-side Validations

Now you want to validate it, the first step would be to validate server-side, since we can't trust the client we should always start here.

routes/something.tsx
export async function action({ request }: ActionFunctionArgs) { let formData = await request.formData() let body = validateFormData(formData) await doSomething(body) return { status: "success" as const } }

Here validateFormData could use Zod to validate the title, slug, excerpt and content, the result is a correctly body with what doSomething will expect and with everything validated.

HTML Validations

The next step would be to add client-side navigations, here the simplest way is to leverage HTML5 to do the validations, e.g.

<form method="post">
  <input name="title" required />
  <input name="slug" required />
  <textarea name="excerpt" />
  <textarea name="content" required /> 
</form>

Client-side Validations

And while that will obviously work it has a drawback that it's impossible to control the error UI, and the validations are not that advanced. We could use the Constraints API but it will need extra work.

Usually at this point most apps will include a form library like Conform, remix-hook-forms, react-hook-form and more.

But you don't really need them, specially if you will only use them to run a Zod schema against your form and return the errors, so you can do it yourself.

Instead of just exporting an action we could export a clientAction, this function will be called before your action and it can even prevent running the server action, and of course we can do validations here the same way we do on action.

routes/something.tsx
export async function clientAction({ request, serverAction }: ClientActionFunctionArgs) { let formData = await request.formData() validateFormData(formData) return await serverAction<typeof action>() }

With this short code we parsed the request on the browser, got the FormData, and validate it.

Any validation error could be thrown and the result of the server action will be returned from the clientAction.

Now combine Zod errors with something like React Aria Components' Form validations and you can show the errors on the UI with a better design.

With all of this could build a progressively enhanced form that starts simple with a normal form and server-side validations, it can improve by adding HTML validations, and then after JS loaded use clientAction to validate client-side and give feedback quickly as you don't need a server roundtrip.

Every validation layer is build on top of the previous one.