Skip to content

Commit e6a311a

Browse files
committed
Add docs for new methods
1 parent 76b9451 commit e6a311a

File tree

1 file changed

+193
-0
lines changed

1 file changed

+193
-0
lines changed

src/content/docs/uselens.mdx

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,10 @@ These are the core methods available on every lens instance:
7777
| [`reflect`](#reflect) | Transform and reshape lens structure | `Lens<NewStructure>` |
7878
| [`map`](#map) | Iterate over array fields (with useFieldArray) | `R[]` |
7979
| [`interop`](#interop) | Connect to React Hook Form's control system | `{ control, name }` |
80+
| [`narrow`](#narrow) | Type-safe narrowing of union types | `Lens<SubType>` |
81+
| [`assert`](#assert) | Runtime type assertion for type narrowing | `void` |
82+
| [`defined`](#defined) | Exclude null and undefined from lens type | `Lens<NonNullable>` |
83+
| [`cast`](#cast) | Force type change (unsafe) | `Lens<NewType>` |
8084
8185
---
8286
@@ -344,6 +348,195 @@ function ControlledInput({ lens }: { lens: Lens<string> }) {
344348
}
345349
```
346350

351+
<Admonition type="info" title="Type System Escape Hatches">
352+
353+
The `narrow`, `assert`, `defined`, and `cast` methods serve as escape hatches for current TypeScript limitations with lens type compatibility. These methods address scenarios where you need to pass lenses with wider types to components expecting narrower types.
354+
355+
These workarounds will become less necessary once [issue #38](https://github.com/react-hook-form/lenses/issues/38) is resolved, which aims to improve lens type variance to allow more natural type narrowing and component composition.
356+
357+
</Admonition>
358+
359+
### narrow {#narrow}
360+
361+
The `narrow` method provides type-safe narrowing of union types, allowing you to tell the type system which branch of a union you want to work with. This is particularly useful when working with discriminated unions or optional values.
362+
363+
#### Manual Type Narrowing
364+
365+
Use the single generic parameter to manually narrow the type when you know (by external logic) what the value should be:
366+
367+
```tsx copy
368+
// Lens<string | number>
369+
const unionLens = lens.focus("optionalField")
370+
371+
// Narrow to string when you know it's a string
372+
const stringLens = unionLens.narrow<string>()
373+
// Now: Lens<string>
374+
```
375+
376+
#### Discriminated Union Narrowing
377+
378+
Use the discriminant overload to narrow based on a specific property value:
379+
380+
```tsx copy
381+
type Animal =
382+
| { type: 'dog'; breed: string }
383+
| { type: 'cat'; indoor: boolean }
384+
385+
const animalLens: Lens<Animal> = lens.focus("pet")
386+
387+
// Narrow to Dog type using discriminant
388+
const dogLens = animalLens.narrow('type', 'dog')
389+
// Now: Lens<{ type: 'dog'; breed: string }>
390+
391+
const breedLens = dogLens.focus("breed")
392+
// Type-safe access to dog-specific properties
393+
```
394+
395+
<Admonition type="important" title="Type Safety">
396+
397+
The `narrow` method performs type-level operations only. It doesn't validate the runtime value - use it when you have external guarantees about the value's type (e.g., from validation, conditional rendering, or runtime checks).
398+
399+
</Admonition>
400+
401+
### assert {#assert}
402+
403+
The `assert` method provides runtime type assertions that convince TypeScript the current lens is already the desired subtype. Unlike `narrow`, this is a type assertion that modifies the current lens instance.
404+
405+
#### Manual Type Assertion
406+
407+
Use the generic parameter to assert the lens is already the desired type:
408+
409+
```tsx copy
410+
function processString(lens: Lens<string>) {
411+
// Work with string lens
412+
}
413+
414+
const maybeLens: Lens<string | undefined> = lens.focus("optional")
415+
416+
// After your runtime check
417+
if (value !== undefined) {
418+
maybeLens.assert<string>()
419+
processString(maybeLens) // Now TypeScript knows it's Lens<string>
420+
}
421+
```
422+
423+
#### Discriminant-Based Assertion
424+
425+
Use the discriminant overload when you're in a conditional branch:
426+
427+
```tsx copy
428+
type Status =
429+
| { type: 'loading' }
430+
| { type: 'success'; data: string }
431+
| { type: 'error'; message: string }
432+
433+
const statusLens: Lens<Status> = lens.focus("status")
434+
435+
// In a conditional branch
436+
if (selected.type === 'success') {
437+
statusLens.assert('type', 'success')
438+
// Within this block, statusLens is Lens<{ type: 'success'; data: string }>
439+
const dataLens = statusLens.focus("data") // Type-safe access
440+
}
441+
```
442+
443+
<Admonition type="warning" title="Runtime Safety">
444+
445+
`assert` is a type-only operation that doesn't perform runtime validation. Ensure your assertions are backed by proper runtime checks to avoid type safety violations.
446+
447+
</Admonition>
448+
449+
### defined {#defined}
450+
451+
The `defined` method is a convenience function that narrows the lens type to exclude `null` and `undefined` values. This is equivalent to using `narrow<NonNullable<T>>()` but provides a more expressive API.
452+
453+
```tsx copy
454+
const optionalLens: Lens<string | null | undefined> = lens.focus("optional")
455+
456+
// Remove null and undefined from the type
457+
const definedLens = optionalLens.defined()
458+
// Now: Lens<string>
459+
460+
// Use after validation
461+
if (value != null) {
462+
const safeLens = optionalLens.defined()
463+
// Work with guaranteed non-null value
464+
}
465+
```
466+
467+
**Common use cases:**
468+
469+
```tsx copy
470+
// Form validation
471+
const emailLens = lens.focus("email") // Lens<string | undefined>
472+
473+
function validateEmail(email: string) {
474+
// validation logic
475+
}
476+
477+
// After confirming value exists
478+
if (formState.isValid) {
479+
const validEmailLens = emailLens.defined()
480+
// Pass to functions expecting non-null values
481+
validateEmail(validEmailLens.interop().control.getValues())
482+
}
483+
```
484+
485+
### cast {#cast}
486+
487+
The `cast` method forcefully changes the lens type to a new type, regardless of compatibility with the original type. This is a powerful but potentially **unsafe** operation that should be used with extreme caution.
488+
489+
```tsx copy
490+
// Cast from unknown/any to specific type
491+
const unknownLens: Lens<unknown> = lens.focus("dynamicData")
492+
const stringLens = unknownLens.cast<string>()
493+
// Now: Lens<string>
494+
495+
// Cast between incompatible types (dangerous!)
496+
const numberLens: Lens<number> = lens.focus("count")
497+
const stringLens = numberLens.cast<string>()
498+
// Type system now thinks it's Lens<string>, but runtime value is still number
499+
```
500+
501+
**Safe usage patterns:**
502+
503+
```tsx copy
504+
// Working with external APIs returning 'any'
505+
function processApiData(data: any) {
506+
const apiLens = LensCore.create(data)
507+
508+
// Cast after runtime validation
509+
if (typeof data.user === 'object' && data.user !== null) {
510+
const userLens = apiLens.focus("user").cast<User>()
511+
return <UserProfile lens={userLens} />
512+
}
513+
}
514+
515+
// Type narrowing when you have more information
516+
interface BaseConfig {
517+
type: string
518+
}
519+
520+
interface DatabaseConfig extends BaseConfig {
521+
type: 'database'
522+
connectionString: string
523+
}
524+
525+
const configLens: Lens<BaseConfig> = lens.focus("config")
526+
527+
// After checking the type at runtime
528+
if (config.type === 'database') {
529+
const dbConfigLens = configLens.cast<DatabaseConfig>()
530+
// Now can access database-specific properties
531+
}
532+
```
533+
534+
<Admonition type="danger" title="Use with Extreme Caution">
535+
536+
`cast` bypasses TypeScript's type system entirely. It can lead to runtime errors if the underlying data doesn't match the asserted type. Always validate data at runtime before using `cast`, or prefer safer alternatives like `narrow` when possible.
537+
538+
</Admonition>
539+
347540
### useFieldArray
348541

349542
Import the enhanced `useFieldArray` from `@hookform/lenses/rhf` for seamless array handling with lenses.

0 commit comments

Comments
 (0)