backend_lecture6_example
  1. src
  2. lib
  3. useZodValidatedForm.ts
import type {Path, UseFormProps, UseFormReturn} from 'react-hook-form'
import {useForm} from 'react-hook-form'
import type {ZodObject} from 'zod/v4'
import {useActionState, useEffect} from 'react'
import type {FormAction, FormActionDispatch, FormActionResponse} from '@/models/serverFunctions'
import {standardSchemaResolver} from '@hookform/resolvers/standard-schema'
import type {input, output} from 'zod'

const initialFormState = {
  success: false,
}

type UseZodValidatedFormReturn<Schema extends ZodObject, ReturnType> = [
  UseFormReturn<input<Schema>, unknown, output<Schema>>,
  FormActionDispatch,
  FormActionResponse<ReturnType>,
  boolean,
]

export function useZodValidatedForm<Schema extends ZodObject, ReturnType>(
  schema: Schema,
  action: FormAction<ReturnType>,
  props?: UseFormProps<input<Schema>, unknown, output<Schema>>,
): UseZodValidatedFormReturn<Schema, ReturnType> {
  const [state, dispatch, isPending] = useActionState(action, initialFormState as FormActionResponse<ReturnType>)
  const form = useForm<input<Schema>, unknown, output<Schema>>({
    resolver: standardSchemaResolver(schema),
    ...props,
  })

  useEffect(() => {
    if (state?.submittedData) {
      // Reset the form with the data which was returned from the server.
      form.reset(state.submittedData as input<Schema>)
    }
    if (state?.errors) {
      Object.keys(state.errors).forEach(field =>
        form.setError(field === 'errors' ? 'root' : (field as Path<input<Schema>>), {
          type: 'manual',
          message: state.errors![field]?.join(', '),
        }),
      )
    }
  }, [state, form])

  return [form, dispatch, state.data as FormActionResponse<ReturnType>, isPending]
}