Check out our talk at Remix Conf!

Async validation

In this example, we add an async username avaliability check to our form. We also validate it on the server, of course ๐Ÿ™‚

import { InputError } from 'domain-functions'

const schema = z.object({
  username: z.string().nonempty(),
  password: z.string().nonempty(),
})

const takenUsernames = ['foo', 'bar']

export const loader: LoaderFunction = ({ request }) => {
  const url = new URL(request.url)
  const username = url.searchParams.get('username')

  if (username && takenUsernames.includes(username)) {
    return json({ message: 'Already taken' })
  }

  return null
}

const mutation = makeDomainFunction(schema)(async (values) => {
  if (takenUsernames.includes(values.username)) {
    throw new InputError('Already taken', 'username')
  }

  return values
})

export const action: ActionFunction = async ({ request }) =>
  formAction({ request, schema, mutation })

export default function Component() {
  const fetcher = useFetcher()
  const message = fetcher.data?.message

  return (
    <Form schema={schema}>
      {({ Field, Errors, Button, clearErrors }) => (
        <>
          <Field name="username">
            {({ Label, Input, Errors, Error }) => (
              <>
                <Label />
                <Input
                  onChange={(event) => {
                    clearErrors('username')

                    fetcher.load(
                      `/examples/forms/async-validation?username=${event.target.value}`,
                    )
                  }}
                />
                <Errors />
                {message && <Error>{message}</Error>}
              </>
            )}
          </Field>
          <Field name="password" />
          <Errors />
          <Button disabled={message} />
        </>
      )}
    </Form>
  )
}