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 'composable-functions'

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

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

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

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

  return null
}

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

  return values
})

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

export default function Component() {
  const fetcher = useFetcher<{ message: string }>()
  const message = fetcher.data?.message

  return (
    <SchemaForm 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={Boolean(message)} />
        </>
      )}
    </SchemaForm>
  )
}