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