Skip to content

Commit c9e4c65

Browse files
committed
Fix lens nested childs bug
Lazy things got complicated
1 parent 2e29fe0 commit c9e4c65

File tree

6 files changed

+110
-71
lines changed

6 files changed

+110
-71
lines changed

packages/core/src/keyval.ts

+90-65
Original file line numberDiff line numberDiff line change
@@ -100,62 +100,71 @@ export function keyval<Input, ModelEnhance, Api, Shape>(
100100
return options.clone(true, options.cloneOf || options);
101101
}
102102

103+
type Enriched = Input & ModelEnhance;
104+
type Output = {
105+
[K in keyof ModelEnhance]:
106+
| Store<ModelEnhance[K]>
107+
| Keyval<any, ModelEnhance[K], any, any>;
108+
};
109+
type KeyvalListState = ListState<Enriched, Output, Api>;
110+
103111
const init = (
104112
isClone: boolean,
105-
cloneOf: Keyval<any, any, any, any> | null,
113+
cloneOf: Keyval<Input, Enriched, Api, Shape> | null,
106114
) => {
107-
type Enriched = Input & ModelEnhance;
108-
type Output = {
109-
[K in keyof ModelEnhance]:
110-
| Store<ModelEnhance[K]>
111-
| Keyval<any, ModelEnhance[K], any, any>;
112-
};
113-
type KeyvalListState = ListState<Enriched, Output, Api>;
114115
const $entities = createStore<KeyvalListState>({
115116
items: [],
116117
instances: [],
117118
keys: [],
118119
});
119120
const $items = $entities.map(({ items }) => items);
120121
const $keys = $entities.map(({ keys }) => keys);
122+
let getKeyRaw:
123+
| keyof Input
124+
| ((entity: Input) => string | number)
125+
| undefined;
126+
let create:
127+
| void
128+
| ((config: { onMount: Event<void> }) => {
129+
state: unknown;
130+
api?: unknown;
131+
key: string;
132+
optional?: string[];
133+
});
134+
let shape: Shape;
135+
if (typeof options === 'function') {
136+
create = options as any;
137+
} else {
138+
({
139+
key: getKeyRaw,
140+
shape = {} as Shape,
141+
create,
142+
} = options as Exclude<typeof options, Keyval<any, any, any, any>>);
143+
}
144+
const {
145+
getKey: getKeyClone,
146+
keyField: keyFieldClone,
147+
structShape: structShapeClone,
148+
defaultState: defaultStateClone,
149+
} = cloneOf?.getCloneData() ?? {};
121150
return lazyInit(
122151
{
123152
type: 'keyval',
124153
api: 0,
125154
__lens: 0,
126-
__struct: 0,
155+
__struct: structShapeClone,
127156
$items,
128157
$keys,
129158
__$listState: $entities,
130-
defaultState: () => null as any,
159+
defaultState: defaultStateClone,
131160
edit: 0,
132161
editField: 0,
133162
clone: init,
134163
isClone,
135164
cloneOf,
165+
getCloneData: cloneOf?.getCloneData ?? (() => null as any),
136166
} as any as Keyval<Input, Input & ModelEnhance, Api, Shape>,
137167
() => {
138-
let create:
139-
| void
140-
| ((config: { onMount: Event<void> }) => {
141-
state: unknown;
142-
api?: unknown;
143-
key: string;
144-
optional?: string[];
145-
});
146-
// @ts-expect-error bad implementation
147-
let getKeyRaw;
148-
let shape: Shape;
149-
if (typeof options === 'function') {
150-
create = options as any;
151-
} else {
152-
({
153-
key: getKeyRaw,
154-
shape = {} as Shape,
155-
create,
156-
} = options as Exclude<typeof options, Keyval<any, any, any, any>>);
157-
}
158-
159168
let kvModel:
160169
| Model<
161170
{
@@ -169,20 +178,55 @@ export function keyval<Input, ModelEnhance, Api, Shape>(
169178

170179
if (create) {
171180
// @ts-expect-error typecast
172-
kvModel = model({ create });
181+
kvModel = model({ create, isClone });
173182
}
183+
const getKey =
184+
getKeyClone ??
185+
(!kvModel
186+
? typeof getKeyRaw === 'function'
187+
? getKeyRaw
188+
: // @ts-expect-error bad implementation
189+
(entity: Input) => entity[getKeyRaw] as string | number
190+
: (entity: Input) => entity[kvModel.keyField] as string | number);
191+
const keyField =
192+
keyFieldClone ??
193+
(!kvModel
194+
? typeof getKeyRaw === 'function' || getKeyRaw === undefined
195+
? null
196+
: getKeyRaw
197+
: kvModel.keyField);
198+
const structShape =
199+
structShapeClone ??
200+
(kvModel
201+
? ({
202+
type: 'structKeyval',
203+
getKey,
204+
shape: kvModel.__struct!.shape,
205+
defaultItem: kvModel.defaultState,
206+
} as StructKeyval)
207+
: shape!
208+
? ({
209+
type: 'structKeyval',
210+
getKey,
211+
shape: {},
212+
// TODO add support for .itemStore
213+
defaultItem: () => null,
214+
} as StructKeyval)
215+
: (null as any as StructKeyval));
174216

175-
const getKey = !kvModel
176-
? typeof getKeyRaw === 'function'
177-
? getKeyRaw
178-
: // @ts-expect-error bad implementation
179-
(entity: Input) => entity[getKeyRaw] as string | number
180-
: (entity: Input) => entity[kvModel.keyField] as string | number;
181-
const keyField = !kvModel
182-
? typeof getKeyRaw === 'function' || getKeyRaw === undefined
183-
? null
184-
: getKeyRaw
185-
: kvModel.keyField;
217+
const defaultState =
218+
defaultStateClone ?? (() => kvModel?.defaultState() ?? (null as any));
219+
220+
const getCloneData =
221+
cloneOf?.getCloneData ??
222+
(() => {
223+
return {
224+
defaultState,
225+
structShape,
226+
keyField,
227+
getKey,
228+
};
229+
});
186230

187231
const api = createInstanceApi($entities, kvModel);
188232
const editApi = createEditApi(
@@ -194,41 +238,22 @@ export function keyval<Input, ModelEnhance, Api, Shape>(
194238
);
195239
const editField = createEditFieldApi(keyField, kvModel, editApi.update);
196240

197-
const structShape = kvModel
198-
? ({
199-
type: 'structKeyval',
200-
getKey,
201-
shape: kvModel.__struct!.shape,
202-
defaultItem: kvModel.defaultState,
203-
} as StructKeyval)
204-
: shape!
205-
? ({
206-
type: 'structKeyval',
207-
getKey,
208-
shape: {},
209-
// TODO add support for .itemStore
210-
defaultItem: () => null,
211-
} as StructKeyval)
212-
: (null as any as StructKeyval);
213-
214241
return {
215242
type: 'keyval' as const,
216243
api: api as any,
217-
// @ts-expect-error bad implementation
218244
__lens: shape,
219245
__struct: structShape,
220246
$items,
221247
$keys,
222248
__$listState: $entities as any,
223-
defaultState() {
224-
return kvModel?.defaultState() ?? (null as any);
225-
},
249+
defaultState,
226250
edit: editApi,
227251
editField,
228252
clone: init,
229253
isClone,
230254
cloneOf,
231-
};
255+
getCloneData,
256+
} as Keyval<Input, Input & ModelEnhance, Api, Shape>;
232257
},
233258
);
234259
};

packages/core/src/lazy.ts

+5
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,23 @@ type TypeMap = {
1717

1818
export let currentSkipLazyCb = true;
1919
export let isRoot = true;
20+
export let isInitClone = false;
2021

2122
export function callInLazyStack<T extends () => any>(
2223
fn: T,
2324
skipLazyCb: boolean,
25+
isClone: boolean,
2426
): ReturnType<T> {
2527
const prevLazyCb = currentSkipLazyCb;
2628
const prevIsRoot = isRoot;
29+
const prevIsInitClone = isInitClone;
2730
currentSkipLazyCb = skipLazyCb;
2831
isRoot = false;
32+
isInitClone = isClone;
2933
const result = fn();
3034
currentSkipLazyCb = prevLazyCb;
3135
isRoot = prevIsRoot;
36+
isInitClone = prevIsInitClone;
3237
return result;
3338
}
3439

packages/core/src/lazyInit.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { currentSkipLazyCb, isRoot } from './lazy';
1+
import { currentSkipLazyCb, isRoot, isInitClone } from './lazy';
22

33
const queue: InitTask<any>[] = [];
44
let scheduled = false;
@@ -17,6 +17,7 @@ const ignore = [
1717
'__$listState',
1818
'$items',
1919
'$keys',
20+
'getCloneData',
2021
];
2122

2223
function runQueue() {
@@ -39,7 +40,7 @@ function runQueue() {
3940
}
4041

4142
export function lazyInit<T extends object>(target: T, init: () => T): T {
42-
if (currentSkipLazyCb && !isRoot) {
43+
if (currentSkipLazyCb && !isRoot && isInitClone) {
4344
return target;
4445
}
4546
const task: InitTask<T> = { target, init, initialized: false };

packages/core/src/model.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ export function model<
4444
} = {},
4545
>({
4646
create,
47+
isClone,
4748
}: {
4849
create: () => {
4950
state: Output;
@@ -52,6 +53,7 @@ export function model<
5253
optional?: string[];
5354
onMount?: EventCallable<void>;
5455
};
56+
isClone: boolean;
5557
}): Model<
5658
Input,
5759
Show<{
@@ -78,7 +80,7 @@ export function model<
7880
onMount,
7981
...rest
8082
} = withRegion(region, () => {
81-
return callInLazyStack(() => create(), true);
83+
return callInLazyStack(() => create(), true, isClone);
8284
});
8385

8486
if (Object.keys(rest).length > 0) {
@@ -156,7 +158,7 @@ export function model<
156158
if (!defaultState) {
157159
const region = createRegionalNode(false);
158160
const { state = {} } = withRegion(region, () =>
159-
callInLazyStack(() => create(), false),
161+
callInLazyStack(() => create(), false, false),
160162
);
161163
defaultState = {};
162164
for (const key in state) {

packages/core/src/spawn.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ export function spawn<
8484
state: storeOutputs = {},
8585
api: apiOutputs = {},
8686
onMount,
87-
} = callInLazyStack(() => model.create(), false);
87+
} = callInLazyStack(() => model.create(), false, false);
8888
const resultShape = {
8989
...storeOutputs,
9090
} as Output;

packages/core/src/types.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ export type Keyval<Input, Enriched, Api, Shape> = {
240240
//private
241241
clone(
242242
isClone: boolean,
243-
cloneOf: Keyval<any, any, any, any> | null,
243+
cloneOf: Keyval<Input, Enriched, Api, Shape> | null,
244244
): Keyval<Input, Enriched, Api, Shape>;
245245
isClone: boolean;
246246
// private
@@ -256,6 +256,12 @@ export type Keyval<Input, Enriched, Api, Shape> = {
256256
>
257257
>;
258258
cloneOf: Keyval<any, any, any, any> | null;
259+
getCloneData(): {
260+
defaultState(): Enriched;
261+
structShape: StructKeyval;
262+
keyField: keyof Input | null;
263+
getKey(entity: Input): string | number;
264+
};
259265
};
260266

261267
export type StoreContext<T> = {

0 commit comments

Comments
 (0)