Skip to content

Commit dcf69b9

Browse files
committed
Fix caching using when focusing the same field name after reflect (#29)
1 parent a688f22 commit dcf69b9

File tree

4 files changed

+48
-23
lines changed

4 files changed

+48
-23
lines changed

src/LensCore.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { type Control, type FieldValues, get, set } from 'react-hook-form';
22

3-
import type { LensesStorage } from './LensesStorage';
3+
import type { LensesStorage, LensesStorageComplexKey } from './LensesStorage';
44
import type { Lens } from './types';
55

66
export interface LensCoreInteropBinding<T extends FieldValues> {
@@ -18,9 +18,10 @@ export class LensCore<T extends FieldValues> {
1818
public path: string;
1919
public cache?: LensesStorage<T> | undefined;
2020

21-
private isArrayItemReflection?: boolean;
22-
private override?: Record<string, LensCore<T>> | [Record<string, LensCore<T>>];
23-
private interopCache?: LensCoreInteropBinding<T>;
21+
protected isArrayItemReflection?: boolean;
22+
protected override?: Record<string, LensCore<T>> | [Record<string, LensCore<T>>];
23+
protected interopCache?: LensCoreInteropBinding<T>;
24+
protected reflectedKey?: LensesStorageComplexKey;
2425

2526
constructor(control: Control<T>, path: string, cache?: LensesStorage<T> | undefined) {
2627
this.control = control;
@@ -39,7 +40,7 @@ export class LensCore<T extends FieldValues> {
3940
const propString = prop.toString();
4041
const nestedPath = this.path ? `${this.path}.${propString}` : propString;
4142

42-
const fromCache = this.cache?.get(nestedPath);
43+
const fromCache = this.cache?.get(nestedPath, this.reflectedKey);
4344

4445
if (fromCache) {
4546
return fromCache;
@@ -112,11 +113,13 @@ export class LensCore<T extends FieldValues> {
112113
const result = new LensCore(this.control, this.path, this.cache);
113114
template.path = '';
114115
result.override = getter(dictionary, template);
116+
result.reflectedKey = getter;
115117
this.cache?.set(result, this.path, getter);
116118
return result;
117119
} else {
118120
template.override = override;
119121
template.path = this.path;
122+
template.reflectedKey = getter;
120123
this.cache?.set(template, this.path, getter);
121124
return template;
122125
}
@@ -147,7 +150,7 @@ export class LensCore<T extends FieldValues> {
147150
return this.interopCache;
148151
}
149152

150-
private getTransformer(value: unknown): unknown {
153+
protected getTransformer(value: unknown): unknown {
151154
const [template] = Array.isArray(this.override) ? this.override : [this.override];
152155

153156
if (!value || !template) {
@@ -170,7 +173,7 @@ export class LensCore<T extends FieldValues> {
170173
return newValue;
171174
}
172175

173-
private setTransformer(value: unknown): unknown {
176+
protected setTransformer(value: unknown): unknown {
174177
const [template] = Array.isArray(this.override) ? this.override : [this.override];
175178

176179
if (!value || !template) {

src/LensesStorage.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export type LensCache<T extends FieldValues> = Map<string, LensesStorageValue<T>
1515
* Cache storage for lenses.
1616
*/
1717
export class LensesStorage<T extends FieldValues> {
18-
private cache: LensCache<T>;
18+
protected cache: LensCache<T>;
1919

2020
constructor(control: Control<T>) {
2121
this.cache = new Map();

src/types/Lens.ts

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,24 @@ import type { HookFormControlShim } from './Interop';
55
import type { ObjectLens } from './ObjectLens';
66
import type { PrimitiveLens } from './PrimitiveLens';
77

8+
export interface LensBase<T> {
9+
assert: T;
10+
}
11+
12+
/**
13+
* This is a type that allows you to hold the type of a form element.
14+
*
15+
* ```ts
16+
* type LensWithArray = Lens<string[]>;
17+
* type LensWithObject = Lens<{ name: string; age: number }>;
18+
* type LensWithPrimitive = Lens<string>;
19+
* ```
20+
*
21+
* In runtime it has `control` and `name` to use latter in react-hook-form.
22+
* Each time you do `lens.focus('propPath')` it creates a lens that keeps nesting of paths.
23+
*/
24+
export type Lens<T> = LensBase<Exclude<T, null | undefined>> & LensSelector<T>;
25+
826
/**
927
* Why not use `T extends any[] ...`, instead of `[T] extends [any[]]`?:
1028
* Because of @see {@link https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html#distributive-conditional-types}
@@ -23,21 +41,6 @@ export type LensSelector<T> = [T] extends [any[]]
2341
? never
2442
: PrimitiveLens<T>;
2543

26-
/**
27-
* This is a type that allows you to hold the type of a form element.
28-
*
29-
* ```ts
30-
* type LensWithArray = Lens<string[]>;
31-
* type LensWithObject = Lens<{ name: string; age: number }>;
32-
* type LensWithPrimitive = Lens<string>;
33-
* ```
34-
*
35-
* In runtime it has `control` and `name` to use latter in react-hook-form.
36-
* Each time you do `lens.focus('propPath')` it creates a lens that keeps nesting of paths.
37-
*/
38-
39-
export type Lens<T> = { assert: boolean } & LensSelector<T>;
40-
4144
export type LensesDictionary<T> = {
4245
[P in keyof T]: Lens<T[P]>;
4346
};

tests/cache.test.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,22 @@ test('lens cannot be cache by function without useCallback', () => {
4444

4545
expect(a).not.toBe(b);
4646
});
47+
48+
test('interop return non cached name when override', () => {
49+
const { result } = renderHook(() => {
50+
const form = useForm<{ a: string; b: number }>();
51+
const lens = useLens({ control: form.control });
52+
53+
return { lens };
54+
});
55+
56+
const aLens = result.current.lens.focus('a');
57+
const bLens = result.current.lens.focus('b');
58+
59+
expect(aLens.interop().name).toEqual('a');
60+
expect(bLens.interop().name).toEqual('b');
61+
62+
const reflect = result.current.lens.reflect((l) => ({ a: l.b }));
63+
64+
expect(reflect.focus('a').interop().name).toEqual('b');
65+
});

0 commit comments

Comments
 (0)