Capítulo 37 de 39
useLens (from the separate @hookform/lenses package) creates a type-safe functional "lens" connected to a form's control, letting you drill into, reshape, and pass around nested slices of form data without hand-writing Path<T> strings or losing type inference across component boundaries.
npm install @hookform/lenses — not part of core react-hook-form.control; an optional dependency array clears the lens cache and rebuilds when external state changes.lens.focus("profile.email") or lens.focus("users.0.name") — fully typed, autocompletes valid paths and rejects invalid ones.useFieldArray's fields, yielding (value, itemLens, index, array, originLens) per row.{ control, name }; called with a callback passes (control, name) directly; its return value can be spread onto register or passed straight into useController/useFieldArray.narrow<T>() or narrow(discriminant, value) produces a differently-typed lens without a runtime check; assert does the same but mutates the current lens's inferred type in place; defined() is shorthand for narrowing away null/undefined; cast<T>() forces an arbitrary type change with no safety at all. None of these validate anything at runtime — pair them with your own runtime check.const { control } = useForm<MyFormData>()
const lens = useLens({ control })
function ContactsList({ lens }: { lens: Lens<Contact[]> }) {
const { fields, append, remove } = useFieldArray(lens.interop())
return (
<div>
<button onClick={() => append({ name: "", email: "" })}>Add</button>
{lens.map(fields, (value, l, index) => (
<div key={value.id}>
<button onClick={() => remove(index)}>Remove</button>
<input {...l.focus("name").interop((ctrl, name) => ctrl.register(name))} />
</div>
))}
</div>
)
}
focus + interop + useFieldArray combined to build a typed, reusable row component for a dynamic array, without manually templating field-path strings.| Method | Purpose | Returns |
|---|---|---|
focus | drill into a field path | Lens<PathValue> |
reflect | reshape/rename/merge lens structure | Lens<NewStructure> |
map | iterate an array lens with useFieldArray | R[] |
interop | bridge to RHF's { control, name } | object or callback result |
narrow | type-only union narrowing | Lens<SubType> |
assert | type-only assertion, mutates current lens's type | void |
defined | narrow away null/undefined | Lens<NonNullable<T>> |
cast | unchecked forced type change | Lens<NewType> |
cast without a prior runtime check: the type system will trust it completely while the actual value can be anything — reserve it for genuinely untyped boundaries (API responses) after validating shape.reflect with more than one template item: array reflection expects exactly one item as the shape template.narrow/assert as runtime validation: both are compile-time only; a wrong assumption produces a type-safe-looking bug, not a caught error.useLens trades hand-written Path<T> strings for composable, type-checked focus/reflect calls — most useful once forms get deeply nested or components need to accept "some slice of the form" generically.interop() is always the exit point back to vanilla RHF (register, useController, useFieldArray).narrow/assert/defined/cast only affect what TypeScript believes — always back them with your own runtime check.lens.map is built directly on top of it.lens.interop()'s return value plugs straight into it.control object every lens ultimately wraps.