Skip to content

React Hook Form Resolver

If you use React Hook Form, this package now ships a dedicated resolver adapter as a separate subpath export:

javascript
import { useForm } from 'react-hook-form'
import { createSchema } from 'json-rest-schema'
import { jsonRestSchemaResolver } from 'json-rest-schema/react-hook-form'

That import path is intentional. The resolver lives outside the main schema engine so the core library does not become React-specific.

Basic usage

javascript
const profileSchema = createSchema({
  name: { type: 'string', required: true, minLength: 3 },
  role: { type: 'string', defaultTo: 'guest' }
})

const form = useForm({
  resolver: jsonRestSchemaResolver(profileSchema)
})

By default, the resolver uses create semantics for full-form validation.

That means:

  • required fields are enforced
  • defaults are applied on successful full-form validation
  • the resolver itself returns normalized success values for full-form validation

So if the user submits:

javascript
{
  name: '  Alex  '
}

the resolver will hand React Hook Form a successful value object equivalent to:

javascript
{
  name: 'Alex',
  role: 'guest'
}

One real-world nuance matters here: React Hook Form still owns its internal field state. In practice, that means a successful resolver pass does not always mean your submit handler receives a canonical normalized payload directly from RHF's state.

If you need a final REST-ready payload, run one last schema operation in the submit handler:

javascript
const form = useForm({
  resolver: jsonRestSchemaResolver(profileSchema)
})

const onSubmit = rawValues => {
  const { validatedObject, errors } = profileSchema.create(rawValues)
  if (Object.keys(errors).length > 0) return

  saveProfile(validatedObject)
}

That split is intentional:

  • RHF keeps raw interactive field state
  • the schema owns final normalization at the submit boundary
  • the UI is free to avoid aggressive value rewriting while the user is typing

Edit forms and custom operations

If the form is editing an existing resource, use a different operation explicitly.

For a patch-style form:

javascript
const form = useForm({
  resolver: jsonRestSchemaResolver(profileSchema, {
    operation: 'patch'
  })
})

You can also use any custom operation you have registered on the schema:

javascript
const form = useForm({
  resolver: jsonRestSchemaResolver(profileSchema, {
    operation: 'upsert'
  })
})

Field-level re-validation behavior

React Hook Form re-validates one field at a time during user interaction. The resolver uses the core path APIs for that subset validation.

Important behavior:

  • only the selected RHF field names are validated during field-level re-validation
  • sibling required fields do not leak into a single-field re-validation pass
  • by default, field-level re-validation keeps raw form values instead of forcing normalized values back into the UI while the user is typing

That default matters because aggressive normalization during typing can feel bad:

  • trimmed strings can move the cursor
  • number coercion can fight half-complete input
  • defaults can appear before submit

Opting into normalized field-level values

If you explicitly want normalized field values during field-level re-validation, opt in:

javascript
const form = useForm({
  resolver: jsonRestSchemaResolver(
    profileSchema,
    {},
    { normalizeOnFieldValidation: true }
  )
})

This is opt-in on purpose.

Returning raw values on success

If you want successful resolver results to return raw input values instead of normalized values, use raw: true:

javascript
const form = useForm({
  resolver: jsonRestSchemaResolver(
    profileSchema,
    {},
    { raw: true }
  )
})

That applies to successful full-form validation too, so defaults and casts are not pushed into the returned values object.

Error shape

React Hook Form requires hierarchical nested errors for deep paths. The resolver converts the library's flat dotted-path errors into the structure RHF expects.

For example, a schema error like:

javascript
{
  'roles.0.label': {
    field: 'roles.0.label',
    code: 'REQUIRED',
    message: 'Field is required',
    params: {}
  }
}

becomes a React Hook Form error shape equivalent to:

javascript
{
  roles: [
    {
      label: {
        type: 'REQUIRED',
        message: 'Field is required'
      }
    }
  ]
}

Direct array-field errors are placed under RHF's root key for that field array path.

Native browser validation

The resolver also respects React Hook Form's shouldUseNativeValidation option. If RHF asks for native validation, the adapter sets setCustomValidity() / reportValidity() using the schema error messages.

GPL-3.0-only