@isograph/react 0.0.0-main-d319cfe5 → 0.0.0-main-ad899464
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/FragmentReference.d.ts +4 -3
- package/dist/IsographEnvironment.d.ts +18 -12
- package/dist/IsographEnvironment.js +1 -1
- package/dist/areEqualWithDeepComparison.d.ts +3 -0
- package/dist/areEqualWithDeepComparison.js +61 -0
- package/dist/cache.d.ts +5 -2
- package/dist/cache.js +60 -13
- package/dist/componentCache.d.ts +3 -6
- package/dist/componentCache.js +19 -23
- package/dist/index.d.ts +2 -2
- package/dist/index.js +3 -4
- package/dist/read.d.ts +2 -3
- package/dist/read.js +10 -58
- package/dist/useReadAndSubscribe.d.ts +7 -0
- package/dist/useReadAndSubscribe.js +16 -0
- package/dist/useRerenderOnChange.d.ts +4 -0
- package/dist/useRerenderOnChange.js +21 -0
- package/dist/useResult.js +13 -5
- package/package.json +3 -3
- package/src/FragmentReference.ts +3 -1
- package/src/IsographEnvironment.tsx +22 -19
- package/src/areEqualWithDeepComparison.ts +78 -0
- package/src/cache.ts +81 -13
- package/src/componentCache.ts +32 -43
- package/src/index.ts +2 -2
- package/src/read.ts +8 -84
- package/src/reader.ts +1 -2
- package/src/useReadAndSubscribe.ts +25 -0
- package/src/useRerenderOnChange.ts +33 -0
- package/src/useResult.ts +17 -10
- package/dist/useRerenderWhenEncounteredRecordChanges.d.ts +0 -2
- package/dist/useRerenderWhenEncounteredRecordChanges.js +0 -16
- package/src/useRerenderWhenEncounteredRecordChanges.ts +0 -17
@@ -1,41 +1,44 @@
|
|
1
1
|
import { ParentCache } from '@isograph/react-disposable-state';
|
2
2
|
import { RetainedQuery } from './garbageCollection';
|
3
|
+
import { WithEncounteredRecords } from './read';
|
4
|
+
import { FragmentReference } from './FragmentReference';
|
3
5
|
|
4
6
|
export type ComponentOrFieldName = string;
|
5
|
-
type StringifiedArgs = string;
|
6
|
-
type
|
7
|
+
export type StringifiedArgs = string;
|
8
|
+
type ComponentCache = {
|
7
9
|
[key: DataId]: {
|
8
|
-
[key: ComponentOrFieldName]: {
|
9
|
-
[key: StringifiedArgs]: ComponentAndEncounteredRecords;
|
10
|
-
};
|
10
|
+
[key: ComponentOrFieldName]: { [key: StringifiedArgs]: React.FC<any> };
|
11
11
|
};
|
12
12
|
};
|
13
13
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
14
|
+
type FragmentSubscription<TReadFromStore extends Object> = {
|
15
|
+
readonly kind: 'FragmentSubscription';
|
16
|
+
readonly callback: (
|
17
|
+
newEncounteredDataAndRecords: WithEncounteredRecords<TReadFromStore>,
|
18
|
+
) => void;
|
19
|
+
/** The value read out from the previous call to readButDoNotEvaluate */
|
20
|
+
readonly encounteredDataAndRecords: WithEncounteredRecords<TReadFromStore>;
|
21
|
+
readonly fragmentReference: FragmentReference<TReadFromStore, any>;
|
18
22
|
};
|
19
|
-
|
20
|
-
|
21
|
-
callback: () => void;
|
22
|
-
records: Set<DataId> | null;
|
23
|
+
type AnyRecordSubscription = {
|
24
|
+
readonly kind: 'AnyRecords';
|
25
|
+
readonly callback: () => void;
|
23
26
|
};
|
24
|
-
|
27
|
+
|
28
|
+
type Subscription = FragmentSubscription<Object> | AnyRecordSubscription;
|
29
|
+
type Subscriptions = Set<Subscription>;
|
25
30
|
type SuspenseCache = { [index: string]: ParentCache<any> };
|
26
31
|
|
27
32
|
export type IsographEnvironment = {
|
28
33
|
store: IsographStore;
|
29
34
|
networkFunction: IsographNetworkFunction;
|
30
35
|
missingFieldHandler: MissingFieldHandler | null;
|
36
|
+
componentCache: ComponentCache;
|
31
37
|
subscriptions: Subscriptions;
|
38
|
+
suspenseCache: SuspenseCache;
|
32
39
|
retainedQueries: Set<RetainedQuery>;
|
33
40
|
gcBuffer: Array<RetainedQuery>;
|
34
41
|
gcBufferSize: number;
|
35
|
-
|
36
|
-
// These caches should be moved into the store somehow
|
37
|
-
componentAndEncounteredRecordsCache: ComponentAndEncounteredRecordsCache;
|
38
|
-
suspenseCache: SuspenseCache;
|
39
42
|
};
|
40
43
|
|
41
44
|
export type MissingFieldHandler = (
|
@@ -95,7 +98,7 @@ export function createIsographEnvironment(
|
|
95
98
|
store,
|
96
99
|
networkFunction,
|
97
100
|
missingFieldHandler: missingFieldHandler ?? null,
|
98
|
-
|
101
|
+
componentCache: {},
|
99
102
|
subscriptions: new Set(),
|
100
103
|
suspenseCache: {},
|
101
104
|
retainedQueries: new Set(),
|
@@ -0,0 +1,78 @@
|
|
1
|
+
export function areEqualWithDeepComparison(
|
2
|
+
oldItem: unknown,
|
3
|
+
newItem: unknown,
|
4
|
+
): boolean {
|
5
|
+
if (newItem === null) {
|
6
|
+
return oldItem === null;
|
7
|
+
}
|
8
|
+
|
9
|
+
if (newItem === undefined) {
|
10
|
+
return oldItem === undefined;
|
11
|
+
}
|
12
|
+
|
13
|
+
if (Array.isArray(newItem)) {
|
14
|
+
if (!Array.isArray(oldItem)) {
|
15
|
+
return false;
|
16
|
+
}
|
17
|
+
|
18
|
+
return areEqualArraysWithDeepComparison(oldItem, newItem);
|
19
|
+
}
|
20
|
+
|
21
|
+
if (typeof newItem === 'object') {
|
22
|
+
if (typeof oldItem !== 'object') {
|
23
|
+
return false;
|
24
|
+
}
|
25
|
+
|
26
|
+
if (oldItem === null) {
|
27
|
+
return false;
|
28
|
+
}
|
29
|
+
|
30
|
+
return areEqualObjectsWithDeepComparison(oldItem, newItem);
|
31
|
+
}
|
32
|
+
|
33
|
+
return newItem === oldItem;
|
34
|
+
}
|
35
|
+
|
36
|
+
export function areEqualArraysWithDeepComparison(
|
37
|
+
oldItems: ReadonlyArray<unknown>,
|
38
|
+
newItems: ReadonlyArray<unknown>,
|
39
|
+
): boolean {
|
40
|
+
if (newItems.length !== oldItems.length) {
|
41
|
+
return false;
|
42
|
+
}
|
43
|
+
|
44
|
+
for (let i = 0; i < newItems.length; i++) {
|
45
|
+
if (!areEqualWithDeepComparison(oldItems[i], newItems[i])) {
|
46
|
+
return false;
|
47
|
+
}
|
48
|
+
}
|
49
|
+
|
50
|
+
return true;
|
51
|
+
}
|
52
|
+
|
53
|
+
export function areEqualObjectsWithDeepComparison(
|
54
|
+
oldItemObject: object,
|
55
|
+
newItemObject: object,
|
56
|
+
): boolean {
|
57
|
+
const oldKeys = Object.keys(oldItemObject);
|
58
|
+
const newKeys = Object.keys(newItemObject);
|
59
|
+
|
60
|
+
if (oldKeys.length !== newKeys.length) {
|
61
|
+
return false;
|
62
|
+
}
|
63
|
+
|
64
|
+
for (const oldKey of oldKeys) {
|
65
|
+
if (!(oldKey in newItemObject)) {
|
66
|
+
return false;
|
67
|
+
}
|
68
|
+
// @ts-expect-error
|
69
|
+
const oldValue = oldItemObject[oldKey];
|
70
|
+
// @ts-expect-error
|
71
|
+
const newValue = newItemObject[oldKey];
|
72
|
+
|
73
|
+
if (!areEqualWithDeepComparison(oldValue, newValue)) {
|
74
|
+
return false;
|
75
|
+
}
|
76
|
+
}
|
77
|
+
return true;
|
78
|
+
}
|
package/src/cache.ts
CHANGED
@@ -28,6 +28,9 @@ import {
|
|
28
28
|
} from './entrypoint';
|
29
29
|
import { ReaderLinkedField, ReaderScalarField } from './reader';
|
30
30
|
import { Argument, ArgumentValue } from './util';
|
31
|
+
import { WithEncounteredRecords, readButDoNotEvaluate } from './read';
|
32
|
+
import { FragmentReference } from './FragmentReference';
|
33
|
+
import { areEqualObjectsWithDeepComparison } from './areEqualWithDeepComparison';
|
31
34
|
|
32
35
|
declare global {
|
33
36
|
interface Window {
|
@@ -222,22 +225,41 @@ function normalizeData(
|
|
222
225
|
return encounteredIds;
|
223
226
|
}
|
224
227
|
|
225
|
-
export function
|
228
|
+
export function subscribeToAnyChange(
|
226
229
|
environment: IsographEnvironment,
|
227
|
-
encounteredRecords: Set<DataId> | null,
|
228
230
|
callback: () => void,
|
229
231
|
): () => void {
|
230
|
-
const
|
232
|
+
const subscription = {
|
233
|
+
kind: 'AnyRecords',
|
231
234
|
callback,
|
232
|
-
|
233
|
-
|
234
|
-
environment.subscriptions.
|
235
|
-
|
235
|
+
} as const;
|
236
|
+
environment.subscriptions.add(subscription);
|
237
|
+
return () => environment.subscriptions.delete(subscription);
|
238
|
+
}
|
239
|
+
|
240
|
+
export function subscribe<TReadFromStore extends Object>(
|
241
|
+
environment: IsographEnvironment,
|
242
|
+
encounteredDataAndRecords: WithEncounteredRecords<TReadFromStore>,
|
243
|
+
fragmentReference: FragmentReference<TReadFromStore, any>,
|
244
|
+
callback: (
|
245
|
+
newEncounteredDataAndRecords: WithEncounteredRecords<TReadFromStore>,
|
246
|
+
) => void,
|
247
|
+
): () => void {
|
248
|
+
const fragmentSubscription = {
|
249
|
+
kind: 'FragmentSubscription',
|
250
|
+
callback,
|
251
|
+
encounteredDataAndRecords,
|
252
|
+
fragmentReference,
|
253
|
+
} as const;
|
254
|
+
// @ts-expect-error
|
255
|
+
environment.subscriptions.add(fragmentSubscription);
|
256
|
+
// @ts-expect-error
|
257
|
+
return () => environment.subscriptions.delete(fragmentSubscription);
|
236
258
|
}
|
237
259
|
|
238
260
|
export function onNextChange(environment: IsographEnvironment): Promise<void> {
|
239
261
|
return new Promise((resolve) => {
|
240
|
-
const unsubscribe =
|
262
|
+
const unsubscribe = subscribeToAnyChange(environment, () => {
|
241
263
|
unsubscribe();
|
242
264
|
resolve();
|
243
265
|
});
|
@@ -248,11 +270,57 @@ function callSubscriptions(
|
|
248
270
|
environment: IsographEnvironment,
|
249
271
|
recordsEncounteredWhenNormalizing: Set<DataId>,
|
250
272
|
) {
|
251
|
-
environment.subscriptions.forEach((
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
273
|
+
environment.subscriptions.forEach((subscription) => {
|
274
|
+
switch (subscription.kind) {
|
275
|
+
case 'FragmentSubscription': {
|
276
|
+
// TODO if there are multiple components subscribed to the same
|
277
|
+
// fragment, we will call readButNotEvaluate multiple times. We
|
278
|
+
// should fix that.
|
279
|
+
if (
|
280
|
+
hasOverlappingIds(
|
281
|
+
recordsEncounteredWhenNormalizing,
|
282
|
+
subscription.encounteredDataAndRecords.encounteredRecords,
|
283
|
+
)
|
284
|
+
) {
|
285
|
+
const newEncounteredDataAndRecords = readButDoNotEvaluate(
|
286
|
+
environment,
|
287
|
+
subscription.fragmentReference,
|
288
|
+
);
|
289
|
+
|
290
|
+
if (
|
291
|
+
!areEqualObjectsWithDeepComparison(
|
292
|
+
subscription.encounteredDataAndRecords.item,
|
293
|
+
newEncounteredDataAndRecords.item,
|
294
|
+
)
|
295
|
+
) {
|
296
|
+
if (typeof window !== 'undefined' && window.__LOG) {
|
297
|
+
console.log('Deep equality - No', {
|
298
|
+
fragmentReference: subscription.fragmentReference,
|
299
|
+
old: subscription.encounteredDataAndRecords.item,
|
300
|
+
new: newEncounteredDataAndRecords.item,
|
301
|
+
});
|
302
|
+
}
|
303
|
+
// TODO deep compare values
|
304
|
+
subscription.callback(newEncounteredDataAndRecords);
|
305
|
+
} else {
|
306
|
+
if (typeof window !== 'undefined' && window.__LOG) {
|
307
|
+
console.log('Deep equality - Yes', {
|
308
|
+
fragmentReference: subscription.fragmentReference,
|
309
|
+
old: subscription.encounteredDataAndRecords.item,
|
310
|
+
});
|
311
|
+
}
|
312
|
+
}
|
313
|
+
}
|
314
|
+
return;
|
315
|
+
}
|
316
|
+
case 'AnyRecords': {
|
317
|
+
return subscription.callback();
|
318
|
+
}
|
319
|
+
default: {
|
320
|
+
// @ts-expect-error(6133)
|
321
|
+
const _: never = subscription;
|
322
|
+
throw new Error('Unexpected case');
|
323
|
+
}
|
256
324
|
}
|
257
325
|
});
|
258
326
|
}
|
package/src/componentCache.ts
CHANGED
@@ -1,61 +1,50 @@
|
|
1
1
|
import { stableCopy } from './cache';
|
2
|
-
import {
|
3
|
-
import {
|
4
|
-
|
5
|
-
DataId,
|
6
|
-
ComponentAndEncounteredRecords,
|
7
|
-
} from './IsographEnvironment';
|
8
|
-
import { readButDoNotEvaluate } from './read';
|
9
|
-
import { ReaderArtifact } from './reader';
|
10
|
-
import { useRerenderWhenEncounteredRecordChanges } from './useRerenderWhenEncounteredRecordChanges';
|
2
|
+
import { IsographEnvironment } from './IsographEnvironment';
|
3
|
+
import { FragmentReference } from './FragmentReference';
|
4
|
+
import { useReadAndSubscribe } from './useReadAndSubscribe';
|
11
5
|
|
12
6
|
export function getOrCreateCachedComponent(
|
13
7
|
environment: IsographEnvironment,
|
14
|
-
rootId: DataId,
|
15
8
|
componentName: string,
|
16
|
-
|
17
|
-
variables: { [key: string]: string },
|
18
|
-
resolverRefetchQueries: RefetchQueryArtifactWrapper[],
|
9
|
+
fragmentReference: FragmentReference<any, any>,
|
19
10
|
): React.FC<any> {
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
const
|
11
|
+
// cachedComponentsById is a three layer cache: id, then component name, then
|
12
|
+
// stringified args. These three, together, uniquely identify a read at a given
|
13
|
+
// time.
|
14
|
+
const cachedComponentsById = environment.componentCache;
|
15
|
+
|
16
|
+
cachedComponentsById[fragmentReference.root] =
|
17
|
+
cachedComponentsById[fragmentReference.root] ?? {};
|
18
|
+
const componentsByName = cachedComponentsById[fragmentReference.root];
|
19
|
+
|
24
20
|
componentsByName[componentName] = componentsByName[componentName] ?? {};
|
25
21
|
const byArgs = componentsByName[componentName];
|
22
|
+
|
23
|
+
const stringifiedArgs = JSON.stringify(
|
24
|
+
stableCopy(fragmentReference.variables),
|
25
|
+
);
|
26
26
|
byArgs[stringifiedArgs] =
|
27
27
|
byArgs[stringifiedArgs] ??
|
28
|
-
(()
|
28
|
+
(() => {
|
29
29
|
function Component(additionalRuntimeProps: { [key: string]: any }) {
|
30
|
-
const
|
31
|
-
environment,
|
32
|
-
{
|
33
|
-
kind: 'FragmentReference',
|
34
|
-
readerArtifact: readerArtifact,
|
35
|
-
root: rootId,
|
36
|
-
variables,
|
37
|
-
nestedRefetchQueries: resolverRefetchQueries,
|
38
|
-
},
|
39
|
-
byArgs[stringifiedArgs],
|
40
|
-
);
|
41
|
-
|
42
|
-
useRerenderWhenEncounteredRecordChanges(
|
43
|
-
environment,
|
44
|
-
encounteredRecords,
|
45
|
-
);
|
30
|
+
const data = useReadAndSubscribe(environment, fragmentReference);
|
46
31
|
|
47
32
|
if (typeof window !== 'undefined' && window.__LOG) {
|
48
|
-
console.log(
|
33
|
+
console.log(
|
34
|
+
'Component re-rendered: ' +
|
35
|
+
componentName +
|
36
|
+
' ' +
|
37
|
+
fragmentReference.root,
|
38
|
+
);
|
49
39
|
}
|
50
40
|
|
51
|
-
return readerArtifact.resolver(
|
41
|
+
return fragmentReference.readerArtifact.resolver(
|
42
|
+
data,
|
43
|
+
additionalRuntimeProps,
|
44
|
+
);
|
52
45
|
}
|
53
|
-
Component.displayName = `${componentName} (id: ${
|
54
|
-
return
|
55
|
-
component: Component,
|
56
|
-
encounteredRecordsDuringLastRead: null,
|
57
|
-
};
|
46
|
+
Component.displayName = `${componentName} (id: ${fragmentReference.root}) @component`;
|
47
|
+
return Component;
|
58
48
|
})();
|
59
|
-
|
60
|
-
return byArgs[stringifiedArgs].component;
|
49
|
+
return byArgs[stringifiedArgs];
|
61
50
|
}
|
package/src/index.ts
CHANGED
@@ -47,7 +47,7 @@ export {
|
|
47
47
|
RefetchQueryArtifact,
|
48
48
|
RefetchQueryArtifactWrapper,
|
49
49
|
} from './entrypoint';
|
50
|
-
export {
|
50
|
+
export { readButDoNotEvaluate } from './read';
|
51
51
|
export { useResult } from './useResult';
|
52
52
|
export { type FragmentReference } from './FragmentReference';
|
53
53
|
export { useLazyReference } from './useLazyReference';
|
@@ -58,4 +58,4 @@ export {
|
|
58
58
|
ArgumentValue,
|
59
59
|
Arguments,
|
60
60
|
} from './util';
|
61
|
-
export {
|
61
|
+
export { useRerenderOnChange } from './useRerenderOnChange';
|
package/src/read.ts
CHANGED
@@ -4,7 +4,6 @@ import { RefetchQueryArtifactWrapper } from './entrypoint';
|
|
4
4
|
import { FragmentReference } from './FragmentReference';
|
5
5
|
import {
|
6
6
|
assertLink,
|
7
|
-
ComponentAndEncounteredRecords,
|
8
7
|
DataId,
|
9
8
|
defaultMissingFieldHandler,
|
10
9
|
IsographEnvironment,
|
@@ -16,52 +15,9 @@ export type WithEncounteredRecords<T> = {
|
|
16
15
|
item: T;
|
17
16
|
};
|
18
17
|
|
19
|
-
export function read<TReadFromStore extends Object, TClientFieldValue>(
|
20
|
-
environment: IsographEnvironment,
|
21
|
-
fragmentReference: FragmentReference<TReadFromStore, TClientFieldValue>,
|
22
|
-
): WithEncounteredRecords<TClientFieldValue> {
|
23
|
-
const variant = fragmentReference.readerArtifact.variant;
|
24
|
-
if (variant.kind === 'Eager') {
|
25
|
-
const mutableEncounteredRecords = new Set<DataId>();
|
26
|
-
const data = readData(
|
27
|
-
environment,
|
28
|
-
fragmentReference.readerArtifact.readerAst,
|
29
|
-
fragmentReference.root,
|
30
|
-
fragmentReference.variables ?? {},
|
31
|
-
fragmentReference.nestedRefetchQueries,
|
32
|
-
mutableEncounteredRecords,
|
33
|
-
);
|
34
|
-
if (data.kind === 'MissingData') {
|
35
|
-
throw onNextChange(environment);
|
36
|
-
} else {
|
37
|
-
return {
|
38
|
-
encounteredRecords: mutableEncounteredRecords,
|
39
|
-
// @ts-expect-error This not properly typed yet
|
40
|
-
item: fragmentReference.readerArtifact.resolver(data.data),
|
41
|
-
};
|
42
|
-
}
|
43
|
-
} else if (variant.kind === 'Component') {
|
44
|
-
return {
|
45
|
-
// @ts-ignore
|
46
|
-
item: getOrCreateCachedComponent(
|
47
|
-
environment,
|
48
|
-
fragmentReference.root,
|
49
|
-
variant.componentName,
|
50
|
-
fragmentReference.readerArtifact,
|
51
|
-
fragmentReference.variables ?? {},
|
52
|
-
fragmentReference.nestedRefetchQueries,
|
53
|
-
),
|
54
|
-
encounteredRecords: new Set(),
|
55
|
-
};
|
56
|
-
}
|
57
|
-
// Why can't Typescript realize that this is unreachable??
|
58
|
-
throw new Error('This is unreachable');
|
59
|
-
}
|
60
|
-
|
61
18
|
export function readButDoNotEvaluate<TReadFromStore extends Object>(
|
62
19
|
environment: IsographEnvironment,
|
63
20
|
reference: FragmentReference<TReadFromStore, unknown>,
|
64
|
-
encounteredRecordCache: ComponentAndEncounteredRecords,
|
65
21
|
): WithEncounteredRecords<TReadFromStore> {
|
66
22
|
const mutableEncounteredRecords = new Set<DataId>();
|
67
23
|
const response = readData(
|
@@ -78,42 +34,13 @@ export function readButDoNotEvaluate<TReadFromStore extends Object>(
|
|
78
34
|
if (response.kind === 'MissingData') {
|
79
35
|
throw onNextChange(environment);
|
80
36
|
} else {
|
81
|
-
const encounteredRecords = compareAndUpdateEncounteredRecords(
|
82
|
-
encounteredRecordCache,
|
83
|
-
mutableEncounteredRecords,
|
84
|
-
);
|
85
|
-
|
86
37
|
return {
|
87
|
-
encounteredRecords,
|
38
|
+
encounteredRecords: mutableEncounteredRecords,
|
88
39
|
item: response.data,
|
89
40
|
};
|
90
41
|
}
|
91
42
|
}
|
92
43
|
|
93
|
-
function compareAndUpdateEncounteredRecords(
|
94
|
-
encounteredRecordCache: ComponentAndEncounteredRecords,
|
95
|
-
newEncounteredRecords: Set<DataId>,
|
96
|
-
): Set<DataId> {
|
97
|
-
const { encounteredRecordsDuringLastRead } = encounteredRecordCache;
|
98
|
-
if (
|
99
|
-
encounteredRecordsDuringLastRead == null ||
|
100
|
-
encounteredRecordsDuringLastRead.size != newEncounteredRecords.size
|
101
|
-
) {
|
102
|
-
encounteredRecordCache.encounteredRecordsDuringLastRead =
|
103
|
-
newEncounteredRecords;
|
104
|
-
return newEncounteredRecords;
|
105
|
-
} else {
|
106
|
-
for (const item of newEncounteredRecords) {
|
107
|
-
if (!encounteredRecordsDuringLastRead.has(item)) {
|
108
|
-
encounteredRecordCache.encounteredRecordsDuringLastRead =
|
109
|
-
newEncounteredRecords;
|
110
|
-
return newEncounteredRecords;
|
111
|
-
}
|
112
|
-
}
|
113
|
-
return encounteredRecordsDuringLastRead;
|
114
|
-
}
|
115
|
-
}
|
116
|
-
|
117
44
|
type ReadDataResult<TReadFromStore> =
|
118
45
|
| {
|
119
46
|
kind: 'Success';
|
@@ -276,9 +203,6 @@ function readData<TReadFromStore>(
|
|
276
203
|
[],
|
277
204
|
mutableEncounteredRecords,
|
278
205
|
);
|
279
|
-
if (typeof window !== 'undefined' && window.__LOG) {
|
280
|
-
console.log('refetch field data', data, field);
|
281
|
-
}
|
282
206
|
if (data.kind === 'MissingData') {
|
283
207
|
return {
|
284
208
|
kind: 'MissingData',
|
@@ -319,9 +243,6 @@ function readData<TReadFromStore>(
|
|
319
243
|
[],
|
320
244
|
mutableEncounteredRecords,
|
321
245
|
);
|
322
|
-
if (typeof window !== 'undefined' && window.__LOG) {
|
323
|
-
console.log('refetch field data', data, field);
|
324
|
-
}
|
325
246
|
if (data.kind === 'MissingData') {
|
326
247
|
return {
|
327
248
|
kind: 'MissingData',
|
@@ -376,11 +297,14 @@ function readData<TReadFromStore>(
|
|
376
297
|
} else if (variant.kind === 'Component') {
|
377
298
|
target[field.alias] = getOrCreateCachedComponent(
|
378
299
|
environment,
|
379
|
-
root,
|
380
300
|
variant.componentName,
|
381
|
-
|
382
|
-
|
383
|
-
|
301
|
+
{
|
302
|
+
kind: 'FragmentReference',
|
303
|
+
readerArtifact: field.readerArtifact,
|
304
|
+
root,
|
305
|
+
variables,
|
306
|
+
nestedRefetchQueries: resolverRefetchQueries,
|
307
|
+
} as const,
|
384
308
|
);
|
385
309
|
}
|
386
310
|
break;
|
package/src/reader.ts
CHANGED
@@ -5,10 +5,9 @@ import { Arguments } from './util';
|
|
5
5
|
// non-@component and refetch resolvers
|
6
6
|
export type ReaderArtifact<TReadFromStore extends Object, TClientFieldValue> = {
|
7
7
|
kind: 'ReaderArtifact';
|
8
|
-
// The DataID of the parent + the fieldName + the variables are enough
|
9
|
-
// to uniquely identify a call to read(...) at a given time.
|
10
8
|
fieldName: ComponentOrFieldName;
|
11
9
|
readerAst: ReaderAst<TReadFromStore>;
|
10
|
+
// TODO move resolver into the variant
|
12
11
|
resolver: (data: TReadFromStore, runtimeProps: any) => TClientFieldValue;
|
13
12
|
variant: ReaderResolverVariant;
|
14
13
|
};
|
@@ -0,0 +1,25 @@
|
|
1
|
+
import { useState } from 'react';
|
2
|
+
import { FragmentReference } from './FragmentReference';
|
3
|
+
import { IsographEnvironment } from './IsographEnvironment';
|
4
|
+
import { readButDoNotEvaluate } from './read';
|
5
|
+
import { useRerenderOnChange } from './useRerenderOnChange';
|
6
|
+
|
7
|
+
/**
|
8
|
+
* Read the data from a fragment reference and subscribe to updates.
|
9
|
+
* Does not pass the data to the fragment reference's resolver function.
|
10
|
+
*/
|
11
|
+
export function useReadAndSubscribe<TReadFromStore extends Object>(
|
12
|
+
environment: IsographEnvironment,
|
13
|
+
fragmentReference: FragmentReference<TReadFromStore, any>,
|
14
|
+
): TReadFromStore {
|
15
|
+
const [readOutDataAndRecords, setReadOutDataAndRecords] = useState(() =>
|
16
|
+
readButDoNotEvaluate(environment, fragmentReference),
|
17
|
+
);
|
18
|
+
useRerenderOnChange(
|
19
|
+
environment,
|
20
|
+
readOutDataAndRecords,
|
21
|
+
fragmentReference,
|
22
|
+
setReadOutDataAndRecords,
|
23
|
+
);
|
24
|
+
return readOutDataAndRecords.item;
|
25
|
+
}
|
@@ -0,0 +1,33 @@
|
|
1
|
+
import { useEffect } from 'react';
|
2
|
+
import { IsographEnvironment } from './IsographEnvironment';
|
3
|
+
import { subscribe } from './cache';
|
4
|
+
import { WithEncounteredRecords } from './read';
|
5
|
+
import { FragmentReference } from './FragmentReference';
|
6
|
+
|
7
|
+
// TODO add unit tests for this. Add integration tests that test
|
8
|
+
// behavior when the encounteredRecords underneath a fragment change.
|
9
|
+
export function useRerenderOnChange<TReadFromStore extends Object>(
|
10
|
+
environment: IsographEnvironment,
|
11
|
+
encounteredDataAndRecords: WithEncounteredRecords<TReadFromStore>,
|
12
|
+
fragmentReference: FragmentReference<any, any>,
|
13
|
+
setEncounteredDataAndRecords: (
|
14
|
+
data: WithEncounteredRecords<TReadFromStore>,
|
15
|
+
) => void,
|
16
|
+
) {
|
17
|
+
useEffect(() => {
|
18
|
+
return subscribe(
|
19
|
+
environment,
|
20
|
+
encounteredDataAndRecords,
|
21
|
+
fragmentReference,
|
22
|
+
(newEncounteredDataAndRecords) => {
|
23
|
+
setEncounteredDataAndRecords(newEncounteredDataAndRecords);
|
24
|
+
},
|
25
|
+
);
|
26
|
+
// Note: this is an empty array on purpose:
|
27
|
+
// - the fragment reference is stable for the life of the component
|
28
|
+
// - ownership of encounteredDataAndRecords is transferred into the
|
29
|
+
// environment
|
30
|
+
// - though maybe we need to include setEncounteredDataAndRecords in
|
31
|
+
// the dependency array
|
32
|
+
}, []);
|
33
|
+
}
|
package/src/useResult.ts
CHANGED
@@ -1,19 +1,26 @@
|
|
1
1
|
import { useIsographEnvironment } from './IsographEnvironmentProvider';
|
2
|
-
import { read } from './read';
|
3
2
|
import { FragmentReference } from './FragmentReference';
|
4
|
-
import {
|
3
|
+
import { getOrCreateCachedComponent } from './componentCache';
|
4
|
+
import { useReadAndSubscribe } from './useReadAndSubscribe';
|
5
5
|
|
6
6
|
export function useResult<TReadFromStore extends Object, TClientFieldValue>(
|
7
7
|
fragmentReference: FragmentReference<TReadFromStore, TClientFieldValue>,
|
8
8
|
): TClientFieldValue {
|
9
9
|
const environment = useIsographEnvironment();
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
11
|
+
switch (fragmentReference.readerArtifact.variant.kind) {
|
12
|
+
case 'Component': {
|
13
|
+
// @ts-expect-error
|
14
|
+
return getOrCreateCachedComponent(
|
15
|
+
environment,
|
16
|
+
fragmentReference.readerArtifact.variant.componentName,
|
17
|
+
fragmentReference,
|
18
|
+
);
|
19
|
+
}
|
20
|
+
case 'Eager': {
|
21
|
+
const data = useReadAndSubscribe(environment, fragmentReference);
|
22
|
+
// @ts-expect-error resolver is incorrectly typed in ReaderArtifact
|
23
|
+
return fragmentReference.readerArtifact.resolver(data);
|
24
|
+
}
|
25
|
+
}
|
19
26
|
}
|
@@ -1,16 +0,0 @@
|
|
1
|
-
"use strict";
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
-
exports.useRerenderWhenEncounteredRecordChanges = void 0;
|
4
|
-
const react_1 = require("react");
|
5
|
-
const cache_1 = require("./cache");
|
6
|
-
// TODO add unit tests for this. Add integration tests that test
|
7
|
-
// behavior when the encounteredRecords underneath a fragment change.
|
8
|
-
function useRerenderWhenEncounteredRecordChanges(environment, encounteredRecords) {
|
9
|
-
const [, setState] = (0, react_1.useState)();
|
10
|
-
(0, react_1.useEffect)(() => {
|
11
|
-
return (0, cache_1.subscribe)(environment, encounteredRecords, () => {
|
12
|
-
return setState({});
|
13
|
-
});
|
14
|
-
}, [encounteredRecords]);
|
15
|
-
}
|
16
|
-
exports.useRerenderWhenEncounteredRecordChanges = useRerenderWhenEncounteredRecordChanges;
|
@@ -1,17 +0,0 @@
|
|
1
|
-
import { useEffect, useState } from 'react';
|
2
|
-
import { DataId, IsographEnvironment } from './IsographEnvironment';
|
3
|
-
import { subscribe } from './cache';
|
4
|
-
|
5
|
-
// TODO add unit tests for this. Add integration tests that test
|
6
|
-
// behavior when the encounteredRecords underneath a fragment change.
|
7
|
-
export function useRerenderWhenEncounteredRecordChanges(
|
8
|
-
environment: IsographEnvironment,
|
9
|
-
encounteredRecords: Set<DataId>,
|
10
|
-
) {
|
11
|
-
const [, setState] = useState<object | void>();
|
12
|
-
useEffect(() => {
|
13
|
-
return subscribe(environment, encounteredRecords, () => {
|
14
|
-
return setState({});
|
15
|
-
});
|
16
|
-
}, [encounteredRecords]);
|
17
|
-
}
|