Skip to content

Commit 0a1042a

Browse files
committed
Added docs how to extends lens
1 parent e6a311a commit 0a1042a

File tree

1 file changed

+84
-12
lines changed

1 file changed

+84
-12
lines changed

src/content/docs/uselens.mdx

Lines changed: 84 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -378,14 +378,12 @@ const stringLens = unionLens.narrow<string>()
378378
Use the discriminant overload to narrow based on a specific property value:
379379

380380
```tsx copy
381-
type Animal =
382-
| { type: 'dog'; breed: string }
383-
| { type: 'cat'; indoor: boolean }
381+
type Animal = { type: "dog"; breed: string } | { type: "cat"; indoor: boolean }
384382

385383
const animalLens: Lens<Animal> = lens.focus("pet")
386384

387385
// Narrow to Dog type using discriminant
388-
const dogLens = animalLens.narrow('type', 'dog')
386+
const dogLens = animalLens.narrow("type", "dog")
389387
// Now: Lens<{ type: 'dog'; breed: string }>
390388

391389
const breedLens = dogLens.focus("breed")
@@ -426,15 +424,15 @@ Use the discriminant overload when you're in a conditional branch:
426424

427425
```tsx copy
428426
type Status =
429-
| { type: 'loading' }
430-
| { type: 'success'; data: string }
431-
| { type: 'error'; message: string }
427+
| { type: "loading" }
428+
| { type: "success"; data: string }
429+
| { type: "error"; message: string }
432430

433431
const statusLens: Lens<Status> = lens.focus("status")
434432

435433
// In a conditional branch
436-
if (selected.type === 'success') {
437-
statusLens.assert('type', 'success')
434+
if (selected.type === "success") {
435+
statusLens.assert("type", "success")
438436
// Within this block, statusLens is Lens<{ type: 'success'; data: string }>
439437
const dataLens = statusLens.focus("data") // Type-safe access
440438
}
@@ -506,7 +504,7 @@ function processApiData(data: any) {
506504
const apiLens = LensCore.create(data)
507505

508506
// Cast after runtime validation
509-
if (typeof data.user === 'object' && data.user !== null) {
507+
if (typeof data.user === "object" && data.user !== null) {
510508
const userLens = apiLens.focus("user").cast<User>()
511509
return <UserProfile lens={userLens} />
512510
}
@@ -518,14 +516,14 @@ interface BaseConfig {
518516
}
519517

520518
interface DatabaseConfig extends BaseConfig {
521-
type: 'database'
519+
type: "database"
522520
connectionString: string
523521
}
524522

525523
const configLens: Lens<BaseConfig> = lens.focus("config")
526524

527525
// After checking the type at runtime
528-
if (config.type === 'database') {
526+
if (config.type === "database") {
529527
const dbConfigLens = configLens.cast<DatabaseConfig>()
530528
// Now can access database-specific properties
531529
}
@@ -798,6 +796,80 @@ function App() {
798796
}
799797
```
800798

799+
#### Extending lenses
800+
801+
You can extend the basic lens functionality by adding custom methods to the `LensBase` interface. This is useful when you need additional methods that aren't available in the default lens API.
802+
803+
For example, let's add a `getValue` method to the lens that allows you to easily retrieve the current form values.
804+
805+
**Step 1: Create the type declarations file**
806+
807+
Create a `lenses.d.ts` file to extend the basic interface with the methods you want:
808+
809+
```typescript
810+
declare module "@hookform/lenses" {
811+
interface LensBase<T> {
812+
getValue(): T
813+
}
814+
}
815+
816+
export {}
817+
```
818+
819+
**Step 2: Create the custom lens core implementation**
820+
821+
Create a `MyLensCore.ts` file with the actual runtime implementation:
822+
823+
```typescript
824+
import type { FieldValues } from "react-hook-form"
825+
import { LensCore } from "@hookform/lenses"
826+
827+
export class MyLensCore<T extends FieldValues> extends LensCore<T> {
828+
public getValue() {
829+
return this.control._formValues
830+
}
831+
}
832+
```
833+
834+
**Step 3: Create the custom hook**
835+
836+
Create a `useMyLens.ts` file that accepts control and returns the lens as usual:
837+
838+
```typescript
839+
import { type DependencyList, useMemo } from "react"
840+
import type { FieldValues } from "react-hook-form"
841+
842+
import { LensesStorage, type Lens, type UseLensProps } from "@hookform/lenses"
843+
import { MyLensCore } from "./MyLensCore"
844+
845+
export function useMyLens<TFieldValues extends FieldValues = FieldValues>(
846+
props: UseLensProps<TFieldValues>,
847+
deps: DependencyList = []
848+
): Lens<TFieldValues> {
849+
return useMemo(() => {
850+
const cache = new LensesStorage(props.control)
851+
const lens = new MyLensCore<TFieldValues>(
852+
props.control,
853+
"",
854+
cache
855+
) as unknown as Lens<TFieldValues>
856+
857+
return lens
858+
}, [props.control, ...deps])
859+
}
860+
```
861+
862+
**Step 4: Use your extended lens**
863+
864+
Now you can use this hook as usual and you have the new method with correct TypeScript support:
865+
866+
```typescript
867+
const lens = useMyLens(form)
868+
lens.getValue() // Your custom method is now available with full type support
869+
```
870+
871+
This pattern allows you to add any custom functionality to lenses while maintaining full type safety and compatibility with the existing lens API.
872+
801873
<Admonition type="tip" title="Questions or Feedback?">
802874

803875
Found a bug or have a feature request? Check out the [GitHub repository](https://github.com/react-hook-form/lenses) to report issues or contribute to the project.

0 commit comments

Comments
 (0)