@isograph/react-disposable-state 0.2.0 → 0.3.1
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/.turbo/turbo-compile-typescript.log +4 -0
- package/README.md +9 -15
- package/dist/CacheItem.d.ts +1 -0
- package/dist/CacheItem.d.ts.map +1 -0
- package/dist/CacheItem.js +2 -2
- package/dist/ParentCache.d.ts +3 -2
- package/dist/ParentCache.d.ts.map +1 -0
- package/dist/ParentCache.js +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1 -1
- package/dist/{useCachedPrecommitValue.d.ts → useCachedResponsivePrecommitValue.d.ts} +11 -9
- package/dist/useCachedResponsivePrecommitValue.d.ts.map +1 -0
- package/dist/{useCachedPrecommitValue.js → useCachedResponsivePrecommitValue.js} +16 -16
- package/dist/useDisposableState.d.ts +2 -1
- package/dist/useDisposableState.d.ts.map +1 -0
- package/dist/useDisposableState.js +7 -6
- package/dist/useHasCommittedRef.d.ts +1 -0
- package/dist/useHasCommittedRef.d.ts.map +1 -0
- package/dist/useHasCommittedRef.js +1 -2
- package/dist/useLazyDisposableState.d.ts +4 -2
- package/dist/useLazyDisposableState.d.ts.map +1 -0
- package/dist/useLazyDisposableState.js +15 -12
- package/dist/useUpdatableDisposableState.d.ts +1 -0
- package/dist/useUpdatableDisposableState.d.ts.map +1 -0
- package/dist/useUpdatableDisposableState.js +2 -2
- package/docs/managing-complex-state.md +32 -28
- package/package.json +9 -9
- package/src/CacheItem.test.ts +5 -5
- package/src/ParentCache.test.ts +2 -2
- package/src/ParentCache.ts +2 -2
- package/src/index.ts +1 -1
- package/src/{useCachedPrecommitValue.test.tsx → useCachedResponsivePrecommitValue.test.tsx} +12 -12
- package/src/{useCachedPrecommitValue.ts → useCachedResponsivePrecommitValue.ts} +17 -17
- package/src/useDisposableState.ts +11 -7
- package/src/useLazyDisposableState.test.tsx +71 -0
- package/src/useLazyDisposableState.ts +25 -15
- package/src/useUpdatableDisposableState.test.tsx +2 -2
- package/tsconfig.json +2 -2
package/README.md
CHANGED
@@ -84,9 +84,9 @@ A hook that:
|
|
84
84
|
|
85
85
|
```typescript
|
86
86
|
const { state }: { state: T } = useLazyDisposableState<T>(
|
87
|
-
parentCache
|
88
|
-
factory
|
89
|
-
options
|
87
|
+
parentCache,
|
88
|
+
factory,
|
89
|
+
options,
|
90
90
|
);
|
91
91
|
```
|
92
92
|
|
@@ -105,11 +105,9 @@ const {
|
|
105
105
|
state,
|
106
106
|
setState,
|
107
107
|
}: {
|
108
|
-
state: T | null
|
109
|
-
setState: (ItemCleanupPair<T>) => void
|
110
|
-
} = useUpdatableDisposableState<T>(
|
111
|
-
options: ?Options,
|
112
|
-
);
|
108
|
+
state: T | null;
|
109
|
+
setState: (pair: ItemCleanupPair<T>) => void;
|
110
|
+
} = useUpdatableDisposableState<T>(options);
|
113
111
|
```
|
114
112
|
|
115
113
|
### `useDisposableState`
|
@@ -123,13 +121,9 @@ const {
|
|
123
121
|
state,
|
124
122
|
setState,
|
125
123
|
}: {
|
126
|
-
state: T
|
127
|
-
setState: (ItemCleanupPair<T>) => void
|
128
|
-
} = useDisposableState<T>(
|
129
|
-
parentCache: ParentCache<T>,
|
130
|
-
factory: Loader<T>,
|
131
|
-
options: ?Options,
|
132
|
-
);
|
124
|
+
state: T;
|
125
|
+
setState: (pair: ItemCleanupPair<T>) => void;
|
126
|
+
} = useDisposableState<T>(parentCache, factory, options);
|
133
127
|
```
|
134
128
|
|
135
129
|
## Miscellaneous notes
|
package/dist/CacheItem.d.ts
CHANGED
@@ -56,3 +56,4 @@ export declare class CacheItem<T> {
|
|
56
56
|
private __maybeExitNotInParentCacheAndNotDisposedState;
|
57
57
|
}
|
58
58
|
export declare function createTemporarilyRetainedCacheItem<T>(factory: Factory<T>, removeFromParentCache: CleanupFn, options: CacheItemOptions | void): [CacheItem<T>, CleanupFn];
|
59
|
+
//# sourceMappingURL=CacheItem.d.ts.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"CacheItem.d.ts","sourceRoot":"","sources":["../src/CacheItem.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,EACT,OAAO,EACP,eAAe,EAChB,MAAM,4BAA4B,CAAC;AAIpC,MAAM,MAAM,2BAA2B,GAAG;IACxC,IAAI,EAAE,6BAA6B,CAAC;CACrC,CAAC;AACF,MAAM,MAAM,8BAA8B,CAAC,CAAC,IAAI;IAC9C,IAAI,EAAE,gCAAgC,CAAC;IACvC,KAAK,EAAE,CAAC,CAAC;IACT,YAAY,EAAE,MAAM,IAAI,CAAC;IAGzB,oBAAoB,EAAE,MAAM,CAAC;CAC9B,CAAC;AACF,MAAM,MAAM,2BAA2B,CAAC,CAAC,IAAI;IAC3C,IAAI,EAAE,6BAA6B,CAAC;IACpC,KAAK,EAAE,CAAC,CAAC;IACT,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,qBAAqB,EAAE,MAAM,IAAI,CAAC;IAGlC,oBAAoB,EAAE,MAAM,CAAC;IAG7B,oBAAoB,EAAE,MAAM,CAAC;CAC9B,CAAC;AAEF,MAAM,MAAM,cAAc,CAAC,CAAC,IACxB,2BAA2B,CAAC,CAAC,CAAC,GAC9B,8BAA8B,CAAC,CAAC,CAAC,GACjC,2BAA2B,CAAC;AAEhC,MAAM,MAAM,gBAAgB,GAAG;IAC7B,mBAAmB,EAAE,MAAM,CAAC;CAC7B,CAAC;AAKF;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,qBAAa,SAAS,CAAC,CAAC;IACtB,OAAO,CAAC,OAAO,CAAoB;IACnC,OAAO,CAAC,SAAS,CAA0B;gBAQzC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,EACnB,qBAAqB,EAAE,SAAS,EAChC,OAAO,EAAE,gBAAgB,GAAG,IAAI;IAgBlC,QAAQ,IAAI,CAAC;IAiBb,4BAA4B,CAC1B,wBAAwB,EAAE,SAAS,GAClC,eAAe,CAAC,CAAC,CAAC,GAAG,IAAI;IA8E5B,eAAe,IAAI,SAAS;IAkE5B,eAAe,IAAI,SAAS;IAoE5B,OAAO,CAAC,2CAA2C;IAoBnD,OAAO,CAAC,8CAA8C;CAUvD;AAED,wBAAgB,kCAAkC,CAAC,CAAC,EAClD,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,EACnB,qBAAqB,EAAE,SAAS,EAChC,OAAO,EAAE,gBAAgB,GAAG,IAAI,GAC/B,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,CAAC,CAI3B"}
|
package/dist/CacheItem.js
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
"use strict";
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
-
exports.
|
3
|
+
exports.CacheItem = void 0;
|
4
|
+
exports.createTemporarilyRetainedCacheItem = createTemporarilyRetainedCacheItem;
|
4
5
|
const DEFAULT_TEMPORARY_RETAIN_TIME = 5000;
|
5
6
|
// TODO don't export this class, only export type (interface) instead
|
6
7
|
// TODO convert cacheitem impl to a getter and setter and free functions
|
@@ -266,4 +267,3 @@ function createTemporarilyRetainedCacheItem(factory, removeFromParentCache, opti
|
|
266
267
|
const disposeTemporaryRetain = cacheItem.temporaryRetain();
|
267
268
|
return [cacheItem, disposeTemporaryRetain];
|
268
269
|
}
|
269
|
-
exports.createTemporarilyRetainedCacheItem = createTemporarilyRetainedCacheItem;
|
package/dist/ParentCache.d.ts
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
import { CacheItem } from './CacheItem';
|
2
1
|
import { CleanupFn, Factory, ItemCleanupPair } from '@isograph/disposable-types';
|
2
|
+
import { CacheItem } from './CacheItem';
|
3
3
|
/**
|
4
4
|
* ParentCache
|
5
5
|
* - A ParentCache can be in two states: populated and unpopulated.
|
@@ -21,7 +21,7 @@ export declare class ParentCache<T> {
|
|
21
21
|
private readonly __factory;
|
22
22
|
constructor(factory: Factory<T>);
|
23
23
|
/**
|
24
|
-
* This is called from
|
24
|
+
* This is called from useCachedResponsivePrecommitValue, when the parent cache is populated
|
25
25
|
* and a previous temporary retain has been disposed. This can occur in scenarios like:
|
26
26
|
* - temporary retain A is created by component B rendering
|
27
27
|
* - temporary retain A expires, emptying the parent cache
|
@@ -37,3 +37,4 @@ export declare class ParentCache<T> {
|
|
37
37
|
get factory(): Factory<T>;
|
38
38
|
isEmpty(): boolean;
|
39
39
|
}
|
40
|
+
//# sourceMappingURL=ParentCache.d.ts.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"ParentCache.d.ts","sourceRoot":"","sources":["../src/ParentCache.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,EACT,OAAO,EACP,eAAe,EAChB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,SAAS,EAAsC,MAAM,aAAa,CAAC;AAK5E;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,WAAW,CAAC,CAAC;IACxB,OAAO,CAAC,WAAW,CAA6B;IAChD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAa;gBAI3B,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;IAI/B;;;;;;;;;OASG;IACH,8BAA8B,IAAI,eAAe,CAAC,CAAC,CAAC,GAAG,IAAI;IAM3D,+BAA+B,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,SAAS,CAAC;IAM/D,OAAO,CAAC,4BAA4B;IA4BpC,KAAK;IAIL,IAAI,OAAO,IAAI,OAAO,CAAC,CAAC,CAAC,CAExB;IAED,OAAO,IAAI,OAAO;CAGnB"}
|
package/dist/ParentCache.js
CHANGED
@@ -28,7 +28,7 @@ class ParentCache {
|
|
28
28
|
this.__factory = factory;
|
29
29
|
}
|
30
30
|
/**
|
31
|
-
* This is called from
|
31
|
+
* This is called from useCachedResponsivePrecommitValue, when the parent cache is populated
|
32
32
|
* and a previous temporary retain has been disposed. This can occur in scenarios like:
|
33
33
|
* - temporary retain A is created by component B rendering
|
34
34
|
* - temporary retain A expires, emptying the parent cache
|
package/dist/index.d.ts
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
export * from '@isograph/disposable-types';
|
2
2
|
export * from './CacheItem';
|
3
3
|
export * from './ParentCache';
|
4
|
-
export * from './
|
4
|
+
export * from './useCachedResponsivePrecommitValue';
|
5
5
|
export * from './useDisposableState';
|
6
6
|
export * from './useHasCommittedRef';
|
7
7
|
export * from './useLazyDisposableState';
|
8
8
|
export * from './useUpdatableDisposableState';
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,4BAA4B,CAAC;AAE3C,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,qCAAqC,CAAC;AACpD,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,0BAA0B,CAAC;AACzC,cAAc,+BAA+B,CAAC"}
|
package/dist/index.js
CHANGED
@@ -17,7 +17,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
__exportStar(require("@isograph/disposable-types"), exports);
|
18
18
|
__exportStar(require("./CacheItem"), exports);
|
19
19
|
__exportStar(require("./ParentCache"), exports);
|
20
|
-
__exportStar(require("./
|
20
|
+
__exportStar(require("./useCachedResponsivePrecommitValue"), exports);
|
21
21
|
__exportStar(require("./useDisposableState"), exports);
|
22
22
|
__exportStar(require("./useHasCommittedRef"), exports);
|
23
23
|
__exportStar(require("./useLazyDisposableState"), exports);
|
@@ -1,10 +1,10 @@
|
|
1
|
-
import { ParentCache } from './ParentCache';
|
2
1
|
import { ItemCleanupPair } from '@isograph/disposable-types';
|
2
|
+
import { ParentCache } from './ParentCache';
|
3
3
|
/**
|
4
|
-
*
|
4
|
+
* useCachedResponsivePrecommitValue<T>
|
5
5
|
* - Takes a mutable parent cache, a factory function, and an onCommit callback.
|
6
|
-
* - Returns T before
|
7
|
-
* - Calls onCommit with the ItemCleanupPair during
|
6
|
+
* - Returns T before commit after every parent cache change, and null afterward.
|
7
|
+
* - Calls onCommit with the ItemCleanupPair during commit after every parent cache change.
|
8
8
|
* - The T from the render phase is only temporarily retained. It may have been
|
9
9
|
* disposed by the time of the commit. If so, this hook checks the parent cache
|
10
10
|
* for another T or creates one, and passes this T to onCommit.
|
@@ -21,16 +21,18 @@ import { ItemCleanupPair } from '@isograph/disposable-types';
|
|
21
21
|
* with a set of variables (e.g. {name: "Matthew"}), and pass in another cache
|
22
22
|
* (e.g. associated with {name: "James"}), which is empty, the hook will fill that
|
23
23
|
* new cache with the factory function.
|
24
|
+
* - Post-commit, passing a different parentCache will reset hook to the pre-commit
|
25
|
+
* state. The cache will return T before commit, then fill the new cache with the
|
26
|
+
* factory function and return null afterwards.
|
24
27
|
*
|
25
28
|
* Passing a different factory:
|
26
29
|
* - Passing a different factory has no effect, except when factory is called,
|
27
|
-
* which is when the parent cache is being filled, or during
|
30
|
+
* which is when the parent cache is being filled, or during commit.
|
28
31
|
*
|
29
32
|
* Passing a different onCommit:
|
30
|
-
* - Passing a different onCommit has no effect, except for during
|
31
|
-
*
|
32
|
-
* Post-commit, all parameters are ignored and the hook returns null.
|
33
|
+
* - Passing a different onCommit has no effect, except for during commit.
|
33
34
|
*/
|
34
|
-
export declare function
|
35
|
+
export declare function useCachedResponsivePrecommitValue<T>(parentCache: ParentCache<T>, onCommit: (pair: ItemCleanupPair<T>) => void): {
|
35
36
|
state: T;
|
36
37
|
} | null;
|
38
|
+
//# sourceMappingURL=useCachedResponsivePrecommitValue.d.ts.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"useCachedResponsivePrecommitValue.d.ts","sourceRoot":"","sources":["../src/useCachedResponsivePrecommitValue.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAE7D,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE5C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,iCAAiC,CAAC,CAAC,EACjD,WAAW,EAAE,WAAW,CAAC,CAAC,CAAC,EAC3B,QAAQ,EAAE,CAAC,IAAI,EAAE,eAAe,CAAC,CAAC,CAAC,KAAK,IAAI,GAC3C;IAAE,KAAK,EAAE,CAAC,CAAA;CAAE,GAAG,IAAI,CA8DrB"}
|
@@ -1,13 +1,12 @@
|
|
1
1
|
'use strict';
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
-
exports.
|
3
|
+
exports.useCachedResponsivePrecommitValue = useCachedResponsivePrecommitValue;
|
4
4
|
const react_1 = require("react");
|
5
|
-
const useHasCommittedRef_1 = require("./useHasCommittedRef");
|
6
5
|
/**
|
7
|
-
*
|
6
|
+
* useCachedResponsivePrecommitValue<T>
|
8
7
|
* - Takes a mutable parent cache, a factory function, and an onCommit callback.
|
9
|
-
* - Returns T before
|
10
|
-
* - Calls onCommit with the ItemCleanupPair during
|
8
|
+
* - Returns T before commit after every parent cache change, and null afterward.
|
9
|
+
* - Calls onCommit with the ItemCleanupPair during commit after every parent cache change.
|
11
10
|
* - The T from the render phase is only temporarily retained. It may have been
|
12
11
|
* disposed by the time of the commit. If so, this hook checks the parent cache
|
13
12
|
* for another T or creates one, and passes this T to onCommit.
|
@@ -24,17 +23,18 @@ const useHasCommittedRef_1 = require("./useHasCommittedRef");
|
|
24
23
|
* with a set of variables (e.g. {name: "Matthew"}), and pass in another cache
|
25
24
|
* (e.g. associated with {name: "James"}), which is empty, the hook will fill that
|
26
25
|
* new cache with the factory function.
|
26
|
+
* - Post-commit, passing a different parentCache will reset hook to the pre-commit
|
27
|
+
* state. The cache will return T before commit, then fill the new cache with the
|
28
|
+
* factory function and return null afterwards.
|
27
29
|
*
|
28
30
|
* Passing a different factory:
|
29
31
|
* - Passing a different factory has no effect, except when factory is called,
|
30
|
-
* which is when the parent cache is being filled, or during
|
32
|
+
* which is when the parent cache is being filled, or during commit.
|
31
33
|
*
|
32
34
|
* Passing a different onCommit:
|
33
|
-
* - Passing a different onCommit has no effect, except for during
|
34
|
-
*
|
35
|
-
* Post-commit, all parameters are ignored and the hook returns null.
|
35
|
+
* - Passing a different onCommit has no effect, except for during commit.
|
36
36
|
*/
|
37
|
-
function
|
37
|
+
function useCachedResponsivePrecommitValue(parentCache, onCommit) {
|
38
38
|
// TODO: there should be two APIs. One in which we always re-render if the
|
39
39
|
// committed item was not returned during the last render, and one in which
|
40
40
|
// we do not. The latter is useful for cases where every disposable item
|
@@ -42,8 +42,10 @@ function useCachedPrecommitValue(parentCache, onCommit) {
|
|
42
42
|
//
|
43
43
|
// This hook is the former, i.e. re-renders if the committed item has changed.
|
44
44
|
const [, rerender] = (0, react_1.useState)(null);
|
45
|
+
const lastCommittedParentCache = (0, react_1.useRef)(null);
|
45
46
|
(0, react_1.useEffect)(() => {
|
46
|
-
|
47
|
+
lastCommittedParentCache.current = parentCache;
|
48
|
+
// On commit, cacheItem may be disposed, because during the render phase,
|
47
49
|
// we only temporarily retained the item, and the temporary retain could have
|
48
50
|
// expired by the time of the commit.
|
49
51
|
//
|
@@ -80,14 +82,12 @@ function useCachedPrecommitValue(parentCache, onCommit) {
|
|
80
82
|
// identically, but must be loaded.)
|
81
83
|
rerender({});
|
82
84
|
}
|
83
|
-
}, []);
|
84
|
-
|
85
|
-
if (hasCommittedRef.current) {
|
85
|
+
}, [parentCache]);
|
86
|
+
if (lastCommittedParentCache.current === parentCache) {
|
86
87
|
return null;
|
87
88
|
}
|
88
|
-
// Safety: item is only safe to use (i.e. guaranteed not to have disposed)
|
89
|
+
// Safety: item is only safe to use (i.e. guaranteed not to have been disposed)
|
89
90
|
// during this tick.
|
90
91
|
const [cacheItem, item, disposeOfTemporaryRetain] = parentCache.getOrPopulateAndTemporaryRetain();
|
91
92
|
return { state: item };
|
92
93
|
}
|
93
|
-
exports.useCachedPrecommitValue = useCachedPrecommitValue;
|
@@ -1,5 +1,5 @@
|
|
1
|
-
import { ParentCache } from './ParentCache';
|
2
1
|
import { ItemCleanupPair } from '@isograph/disposable-types';
|
2
|
+
import { ParentCache } from './ParentCache';
|
3
3
|
import { UnassignedState } from './useUpdatableDisposableState';
|
4
4
|
type UseUpdatableDisposableStateReturnValue<T> = {
|
5
5
|
state: T;
|
@@ -7,3 +7,4 @@ type UseUpdatableDisposableStateReturnValue<T> = {
|
|
7
7
|
};
|
8
8
|
export declare function useDisposableState<T = never>(parentCache: ParentCache<T>): UseUpdatableDisposableStateReturnValue<T>;
|
9
9
|
export {};
|
10
|
+
//# sourceMappingURL=useDisposableState.d.ts.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"useDisposableState.d.ts","sourceRoot":"","sources":["../src/useDisposableState.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAE7D,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE5C,OAAO,EAEL,eAAe,EAEhB,MAAM,+BAA+B,CAAC;AAEvC,KAAK,sCAAsC,CAAC,CAAC,IAAI;IAC/C,KAAK,EAAE,CAAC,CAAC;IACT,QAAQ,EAAE,CAAC,IAAI,EAAE,eAAe,CAAC,OAAO,CAAC,CAAC,EAAE,eAAe,CAAC,CAAC,KAAK,IAAI,CAAC;CACxE,CAAC;AAEF,wBAAgB,kBAAkB,CAAC,CAAC,GAAG,KAAK,EAC1C,WAAW,EAAE,WAAW,CAAC,CAAC,CAAC,GAC1B,sCAAsC,CAAC,CAAC,CAAC,CAgE3C"}
|
@@ -1,13 +1,15 @@
|
|
1
1
|
"use strict";
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
-
exports.useDisposableState =
|
3
|
+
exports.useDisposableState = useDisposableState;
|
4
4
|
const react_1 = require("react");
|
5
|
-
const
|
5
|
+
const useCachedResponsivePrecommitValue_1 = require("./useCachedResponsivePrecommitValue");
|
6
6
|
const useUpdatableDisposableState_1 = require("./useUpdatableDisposableState");
|
7
7
|
function useDisposableState(parentCache) {
|
8
8
|
var _a, _b, _c;
|
9
9
|
const itemCleanupPairRef = (0, react_1.useRef)(null);
|
10
|
-
const preCommitItem = (0,
|
10
|
+
const preCommitItem = (0, useCachedResponsivePrecommitValue_1.useCachedResponsivePrecommitValue)(parentCache, (pair) => {
|
11
|
+
var _a;
|
12
|
+
(_a = itemCleanupPairRef.current) === null || _a === void 0 ? void 0 : _a[1]();
|
11
13
|
itemCleanupPairRef.current = pair;
|
12
14
|
});
|
13
15
|
const { state: stateFromDisposableStateHook, setState } = (0, useUpdatableDisposableState_1.useUpdatableDisposableState)();
|
@@ -44,15 +46,14 @@ function useDisposableState(parentCache) {
|
|
44
46
|
// Note that in the post-commit post-setState state, itemCleanupPairRef
|
45
47
|
// can still be assigned, during the render before the
|
46
48
|
// cleanupItemCleanupPairRefAfterSetState effect is called.
|
47
|
-
const state = (
|
49
|
+
const state = (_b = (_a = (stateFromDisposableStateHook != useUpdatableDisposableState_1.UNASSIGNED_STATE
|
48
50
|
? stateFromDisposableStateHook
|
49
|
-
: null)) !== null && _a !== void 0 ? _a :
|
51
|
+
: null)) !== null && _a !== void 0 ? _a : preCommitItem === null || preCommitItem === void 0 ? void 0 : preCommitItem.state) !== null && _b !== void 0 ? _b : (_c = itemCleanupPairRef.current) === null || _c === void 0 ? void 0 : _c[0];
|
50
52
|
return {
|
51
53
|
state: state,
|
52
54
|
setState,
|
53
55
|
};
|
54
56
|
}
|
55
|
-
exports.useDisposableState = useDisposableState;
|
56
57
|
// @ts-ignore
|
57
58
|
function tsTests() {
|
58
59
|
let x;
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"useHasCommittedRef.d.ts","sourceRoot":"","sources":["../src/useHasCommittedRef.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAqB,MAAM,OAAO,CAAC;AAE5D;;GAEG;AACH,wBAAgB,kBAAkB,IAAI,gBAAgB,CAAC,OAAO,CAAC,CAM9D"}
|
@@ -1,6 +1,6 @@
|
|
1
1
|
"use strict";
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
-
exports.useHasCommittedRef =
|
3
|
+
exports.useHasCommittedRef = useHasCommittedRef;
|
4
4
|
const react_1 = require("react");
|
5
5
|
/**
|
6
6
|
* Returns true if the component has committed, false otherwise.
|
@@ -12,4 +12,3 @@ function useHasCommittedRef() {
|
|
12
12
|
}, []);
|
13
13
|
return hasCommittedRef;
|
14
14
|
}
|
15
|
-
exports.useHasCommittedRef = useHasCommittedRef;
|
@@ -1,13 +1,15 @@
|
|
1
1
|
import { ParentCache } from './ParentCache';
|
2
|
+
import { type UnassignedState } from './useUpdatableDisposableState';
|
2
3
|
/**
|
3
4
|
* useLazyDisposableState<T>
|
4
5
|
* - Takes a mutable parent cache and a factory function
|
5
6
|
* - Returns { state: T }
|
6
7
|
*
|
7
|
-
* This lazily loads the disposable item using
|
8
|
+
* This lazily loads the disposable item using useCachedResponsivePrecommitValue, then
|
8
9
|
* (on commit) sets it in state. The item continues to be returned after
|
9
10
|
* commit and is disposed when the hook unmounts.
|
10
11
|
*/
|
11
|
-
export declare function useLazyDisposableState<T>(parentCache: ParentCache<T
|
12
|
+
export declare function useLazyDisposableState<T>(parentCache: ParentCache<Exclude<T, UnassignedState>>): {
|
12
13
|
state: T;
|
13
14
|
};
|
15
|
+
//# sourceMappingURL=useLazyDisposableState.d.ts.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"useLazyDisposableState.d.ts","sourceRoot":"","sources":["../src/useLazyDisposableState.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE5C,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAErE;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CAAC,CAAC,EACtC,WAAW,EAAE,WAAW,CAAC,OAAO,CAAC,CAAC,EAAE,eAAe,CAAC,CAAC,GACpD;IACD,KAAK,EAAE,CAAC,CAAC;CACV,CAoCA"}
|
@@ -1,31 +1,35 @@
|
|
1
1
|
'use strict';
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
-
exports.useLazyDisposableState =
|
3
|
+
exports.useLazyDisposableState = useLazyDisposableState;
|
4
4
|
const react_1 = require("react");
|
5
|
-
const
|
5
|
+
const useCachedResponsivePrecommitValue_1 = require("./useCachedResponsivePrecommitValue");
|
6
6
|
/**
|
7
7
|
* useLazyDisposableState<T>
|
8
8
|
* - Takes a mutable parent cache and a factory function
|
9
9
|
* - Returns { state: T }
|
10
10
|
*
|
11
|
-
* This lazily loads the disposable item using
|
11
|
+
* This lazily loads the disposable item using useCachedResponsivePrecommitValue, then
|
12
12
|
* (on commit) sets it in state. The item continues to be returned after
|
13
13
|
* commit and is disposed when the hook unmounts.
|
14
14
|
*/
|
15
15
|
function useLazyDisposableState(parentCache) {
|
16
16
|
var _a, _b;
|
17
17
|
const itemCleanupPairRef = (0, react_1.useRef)(null);
|
18
|
-
const preCommitItem = (0,
|
18
|
+
const preCommitItem = (0, useCachedResponsivePrecommitValue_1.useCachedResponsivePrecommitValue)(parentCache, (pair) => {
|
19
|
+
var _a;
|
20
|
+
(_a = itemCleanupPairRef.current) === null || _a === void 0 ? void 0 : _a[1]();
|
19
21
|
itemCleanupPairRef.current = pair;
|
20
22
|
});
|
21
23
|
(0, react_1.useEffect)(() => {
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
24
|
+
return () => {
|
25
|
+
var _a;
|
26
|
+
const cleanupFn = (_a = itemCleanupPairRef.current) === null || _a === void 0 ? void 0 : _a[1];
|
27
|
+
// TODO confirm useEffect is called in order.
|
28
|
+
if (cleanupFn == null) {
|
29
|
+
throw new Error('cleanupFn unexpectedly null. This indicates a bug in react-disposable-state.');
|
30
|
+
}
|
31
|
+
return cleanupFn();
|
32
|
+
};
|
29
33
|
}, []);
|
30
34
|
const returnedItem = (_a = preCommitItem === null || preCommitItem === void 0 ? void 0 : preCommitItem.state) !== null && _a !== void 0 ? _a : (_b = itemCleanupPairRef.current) === null || _b === void 0 ? void 0 : _b[0];
|
31
35
|
if (returnedItem != null) {
|
@@ -36,4 +40,3 @@ function useLazyDisposableState(parentCache) {
|
|
36
40
|
// so during subsequent renders, itemCleanupPairRef.current is non-null.
|
37
41
|
throw new Error('returnedItem was unexpectedly null. This indicates a bug in react-disposable-state.');
|
38
42
|
}
|
39
|
-
exports.useLazyDisposableState = useLazyDisposableState;
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"useUpdatableDisposableState.d.ts","sourceRoot":"","sources":["../src/useUpdatableDisposableState.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAI7D,eAAO,MAAM,gBAAgB,EAAE,OAAO,MAAiB,CAAC;AACxD,MAAM,MAAM,eAAe,GAAG,OAAO,gBAAgB,CAAC;AAEtD,KAAK,sCAAsC,CAAC,CAAC,IAAI;IAC/C,KAAK,EAAE,CAAC,GAAG,eAAe,CAAC;IAC3B,QAAQ,EAAE,CAAC,IAAI,EAAE,eAAe,CAAC,OAAO,CAAC,CAAC,EAAE,eAAe,CAAC,CAAC,KAAK,IAAI,CAAC;CACxE,CAAC;AAoBF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,2BAA2B,CACzC,CAAC,GAAG,KAAK,KACN,sCAAsC,CAAC,CAAC,CAAC,CA0D7C"}
|
@@ -1,6 +1,7 @@
|
|
1
1
|
"use strict";
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
-
exports.
|
3
|
+
exports.UNASSIGNED_STATE = void 0;
|
4
|
+
exports.useUpdatableDisposableState = useUpdatableDisposableState;
|
4
5
|
const react_1 = require("react");
|
5
6
|
const useHasCommittedRef_1 = require("./useHasCommittedRef");
|
6
7
|
exports.UNASSIGNED_STATE = Symbol();
|
@@ -77,7 +78,6 @@ function useUpdatableDisposableState() {
|
|
77
78
|
state: stateICI !== exports.UNASSIGNED_STATE ? stateICI.item : exports.UNASSIGNED_STATE,
|
78
79
|
};
|
79
80
|
}
|
80
|
-
exports.useUpdatableDisposableState = useUpdatableDisposableState;
|
81
81
|
// @ts-ignore
|
82
82
|
function tsTests() {
|
83
83
|
const a = useUpdatableDisposableState();
|
@@ -13,17 +13,19 @@ Consider using `useUpdatableDisposableState` state to manage an array of disposa
|
|
13
13
|
The behavior of `useUpdatableDisposableState` is to entirely dispose of all previously-held items after a new item is set in state. So, if we had
|
14
14
|
|
15
15
|
```js
|
16
|
-
const
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
16
|
+
const component = () => {
|
17
|
+
const { state, setState } = useUpdatableDisposableState();
|
18
|
+
// assume state === [item1], and the cleanup function will dispose item1
|
19
|
+
|
20
|
+
const addItem2ToState = () => {
|
21
|
+
setState([item1, item2], () => {
|
22
|
+
disposeItem1();
|
23
|
+
disposeItem2();
|
24
|
+
});
|
25
|
+
};
|
26
|
+
|
27
|
+
return makePrettyJSX(state[0]);
|
24
28
|
};
|
25
|
-
|
26
|
-
return makePrettyJSX(state[0]);
|
27
29
|
```
|
28
30
|
|
29
31
|
In the above, `item1` would be disposed after the state was updated to be `[item1, item2]`. Meaning, the hook returns an item that has already been disposed. Clearly, this will not do.
|
@@ -51,25 +53,27 @@ Given an undisposed active reference `r1`, one can get a new active reference `r
|
|
51
53
|
Let's use reference counted pointers in the example.
|
52
54
|
|
53
55
|
```js
|
54
|
-
const
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
const
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
56
|
+
const component = () => {
|
57
|
+
const { state, setState } = useUpdatableDisposableState();
|
58
|
+
// assume state === [item1activeReference], and the cleanup function will dispose that active reference
|
59
|
+
|
60
|
+
const addItem2ToState = () => {
|
61
|
+
const [item2, disposeItem2] = createDisposeItem2();
|
62
|
+
const [item2ActiveReference, disposeItem2ActiveReference] =
|
63
|
+
createReferenceCountedPointer([item2, disposeItem2]);
|
64
|
+
|
65
|
+
// get a new active reference to the existing item1
|
66
|
+
const [item1ActiveReference, disposeItem1ActiveReference] = nullthrows(
|
67
|
+
state[0].cloneIfNotDisposed(),
|
68
|
+
);
|
69
|
+
setState([item1ActiveReference, item2ActiveReference], () => {
|
70
|
+
disposeItem1ActiveReference();
|
71
|
+
disposeItem2ActiveReference();
|
72
|
+
});
|
73
|
+
};
|
74
|
+
|
75
|
+
return makePrettyJSX(nullthrows(state[0].getItemIfNotDisposed()));
|
70
76
|
};
|
71
|
-
|
72
|
-
return makePrettyJSX(nullthrows(state[0].getItemIfNotDisposed()));
|
73
77
|
```
|
74
78
|
|
75
79
|
Not the most ergonomic, but it does the job.
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@isograph/react-disposable-state",
|
3
|
-
"version": "0.
|
3
|
+
"version": "0.3.1",
|
4
4
|
"description": "Primitives for managing disposable state in React",
|
5
5
|
"homepage": "https://isograph.dev",
|
6
6
|
"main": "dist/index.js",
|
@@ -8,26 +8,26 @@
|
|
8
8
|
"author": "Isograph Labs",
|
9
9
|
"license": "MIT",
|
10
10
|
"scripts": {
|
11
|
-
"compile": "rm -rf dist/* && tsc -p tsconfig.pkg.json",
|
11
|
+
"compile-typescript": "rm -rf dist/* && tsc -p tsconfig.pkg.json",
|
12
12
|
"compile-watch": "tsc -p tsconfig.pkg.json --watch",
|
13
13
|
"test": "vitest run",
|
14
14
|
"test-watch": "vitest watch",
|
15
15
|
"coverage": "vitest run --coverage",
|
16
16
|
"note": "WE SHOULD ALSO TEST HERE",
|
17
|
-
"prepack": "
|
18
|
-
"tsc": "tsc"
|
17
|
+
"prepack": "pnpm run compile-typescript",
|
18
|
+
"tsc": "tsc",
|
19
|
+
"tsc-force": "tsc --build --clean && tsc --build --force"
|
19
20
|
},
|
20
21
|
"dependencies": {
|
21
|
-
"@isograph/disposable-types": "
|
22
|
+
"@isograph/disposable-types": "*"
|
22
23
|
},
|
23
24
|
"peerDependencies": {
|
24
|
-
"react": "18.
|
25
|
+
"react": "^18.0.0 || ^19.0.0"
|
25
26
|
},
|
26
27
|
"devDependencies": {
|
27
|
-
"@types/react": "
|
28
|
+
"@types/react": "18.3.1",
|
28
29
|
"react-test-renderer": "^18.2.0",
|
29
|
-
"
|
30
|
-
"typescript": "^5.0.3"
|
30
|
+
"typescript": "5.6.3"
|
31
31
|
},
|
32
32
|
"repository": {
|
33
33
|
"type": "git",
|
package/src/CacheItem.test.ts
CHANGED
@@ -1,18 +1,18 @@
|
|
1
|
+
import { ItemCleanupPair } from '@isograph/disposable-types';
|
1
2
|
import {
|
2
|
-
|
3
|
+
afterEach,
|
3
4
|
assert,
|
4
|
-
test,
|
5
|
-
vi,
|
6
5
|
beforeEach,
|
7
|
-
|
6
|
+
describe,
|
8
7
|
expect,
|
8
|
+
test,
|
9
|
+
vi,
|
9
10
|
} from 'vitest';
|
10
11
|
import {
|
11
12
|
CacheItem,
|
12
13
|
CacheItemState,
|
13
14
|
createTemporarilyRetainedCacheItem,
|
14
15
|
} from './CacheItem';
|
15
|
-
import { ItemCleanupPair } from '@isograph/disposable-types';
|
16
16
|
|
17
17
|
function getState<T>(cacheItem: CacheItem<T>): CacheItemState<T> {
|
18
18
|
return (cacheItem as any).__state as CacheItemState<T>;
|
package/src/ParentCache.test.ts
CHANGED
@@ -1,7 +1,7 @@
|
|
1
|
-
import { describe, assert, test, vi, expect } from 'vitest';
|
2
|
-
import { ParentCache } from './ParentCache';
|
3
1
|
import { ItemCleanupPair } from '@isograph/disposable-types';
|
2
|
+
import { assert, describe, expect, test, vi } from 'vitest';
|
4
3
|
import { CacheItem } from './CacheItem';
|
4
|
+
import { ParentCache } from './ParentCache';
|
5
5
|
|
6
6
|
function getValue<T>(cache: ParentCache<T>): CacheItem<T> | null {
|
7
7
|
return (cache as any).__cacheItem as CacheItem<T> | null;
|
package/src/ParentCache.ts
CHANGED
@@ -1,9 +1,9 @@
|
|
1
|
-
import { CacheItem, createTemporarilyRetainedCacheItem } from './CacheItem';
|
2
1
|
import {
|
3
2
|
CleanupFn,
|
4
3
|
Factory,
|
5
4
|
ItemCleanupPair,
|
6
5
|
} from '@isograph/disposable-types';
|
6
|
+
import { CacheItem, createTemporarilyRetainedCacheItem } from './CacheItem';
|
7
7
|
|
8
8
|
// TODO convert cache impl to a getter and setter and free functions
|
9
9
|
// TODO accept options that get passed to CacheItem
|
@@ -35,7 +35,7 @@ export class ParentCache<T> {
|
|
35
35
|
}
|
36
36
|
|
37
37
|
/**
|
38
|
-
* This is called from
|
38
|
+
* This is called from useCachedResponsivePrecommitValue, when the parent cache is populated
|
39
39
|
* and a previous temporary retain has been disposed. This can occur in scenarios like:
|
40
40
|
* - temporary retain A is created by component B rendering
|
41
41
|
* - temporary retain A expires, emptying the parent cache
|
package/src/index.ts
CHANGED
@@ -2,7 +2,7 @@ export * from '@isograph/disposable-types';
|
|
2
2
|
|
3
3
|
export * from './CacheItem';
|
4
4
|
export * from './ParentCache';
|
5
|
-
export * from './
|
5
|
+
export * from './useCachedResponsivePrecommitValue';
|
6
6
|
export * from './useDisposableState';
|
7
7
|
export * from './useHasCommittedRef';
|
8
8
|
export * from './useLazyDisposableState';
|
@@ -1,10 +1,10 @@
|
|
1
|
-
import { describe, test, vi, expect, assert } from 'vitest';
|
2
|
-
import { ParentCache } from './ParentCache';
|
3
1
|
import { ItemCleanupPair } from '@isograph/disposable-types';
|
4
|
-
import { useCachedPrecommitValue } from './useCachedPrecommitValue';
|
5
2
|
import React from 'react';
|
6
3
|
import { create } from 'react-test-renderer';
|
4
|
+
import { assert, describe, expect, test, vi } from 'vitest';
|
7
5
|
import { CacheItem, CacheItemState } from './CacheItem';
|
6
|
+
import { ParentCache } from './ParentCache';
|
7
|
+
import { useCachedResponsivePrecommitValue } from './useCachedResponsivePrecommitValue';
|
8
8
|
|
9
9
|
function getItem<T>(cache: ParentCache<T>): CacheItem<T> | null {
|
10
10
|
return (cache as any).__cacheItem;
|
@@ -60,7 +60,7 @@ async function awaitableCreate(Component, isConcurrent) {
|
|
60
60
|
return element;
|
61
61
|
}
|
62
62
|
|
63
|
-
describe('
|
63
|
+
describe('useCachedResponsivePrecommitValue', () => {
|
64
64
|
test('on initial render, it should call getOrPopulateAndTemporaryRetain', async () => {
|
65
65
|
const disposeItem = vi.fn();
|
66
66
|
const factory = vi.fn(() => {
|
@@ -80,7 +80,7 @@ describe('useCachedPrecommitValue', () => {
|
|
80
80
|
render();
|
81
81
|
React.useEffect(componentCommits);
|
82
82
|
|
83
|
-
const data =
|
83
|
+
const data = useCachedResponsivePrecommitValue(cache, hookOnCommit);
|
84
84
|
|
85
85
|
expect(render).toBeCalledTimes(1);
|
86
86
|
expect(componentCommits).not.toBeCalled();
|
@@ -117,7 +117,7 @@ describe('useCachedPrecommitValue', () => {
|
|
117
117
|
function TestComponent() {
|
118
118
|
render();
|
119
119
|
expect(render).toHaveBeenCalledTimes(1);
|
120
|
-
const data =
|
120
|
+
const data = useCachedResponsivePrecommitValue(cache, hookOnCommit);
|
121
121
|
|
122
122
|
React.useEffect(() => {
|
123
123
|
componentCommits();
|
@@ -155,7 +155,7 @@ describe('useCachedPrecommitValue', () => {
|
|
155
155
|
function TestComponent() {
|
156
156
|
const [, _setState] = React.useState(null);
|
157
157
|
setState = _setState;
|
158
|
-
const value =
|
158
|
+
const value = useCachedResponsivePrecommitValue(cache, hookOnCommit);
|
159
159
|
|
160
160
|
if (initialRender && value !== null) {
|
161
161
|
initialRender = false;
|
@@ -206,7 +206,7 @@ describe('useCachedPrecommitValue', () => {
|
|
206
206
|
let renderCount = 0;
|
207
207
|
function TestComponent() {
|
208
208
|
render();
|
209
|
-
const value =
|
209
|
+
const value = useCachedResponsivePrecommitValue(cache, hookOnCommit);
|
210
210
|
|
211
211
|
expect(value).toEqual({ state: 1 });
|
212
212
|
expect(factory).toHaveBeenCalledTimes(1);
|
@@ -270,7 +270,7 @@ describe('useCachedPrecommitValue', () => {
|
|
270
270
|
const render = vi.fn();
|
271
271
|
function TestComponent() {
|
272
272
|
render();
|
273
|
-
const value =
|
273
|
+
const value = useCachedResponsivePrecommitValue(cache, hookOnCommit);
|
274
274
|
|
275
275
|
expect(value).toEqual({ state: factoryValue });
|
276
276
|
|
@@ -347,7 +347,7 @@ describe('useCachedPrecommitValue', () => {
|
|
347
347
|
function TestComponent() {
|
348
348
|
render();
|
349
349
|
|
350
|
-
|
350
|
+
useCachedResponsivePrecommitValue(cache, hookOnCommit);
|
351
351
|
|
352
352
|
React.useEffect(() => {
|
353
353
|
componentCommits();
|
@@ -435,7 +435,7 @@ describe('useCachedPrecommitValue', () => {
|
|
435
435
|
const render = vi.fn();
|
436
436
|
function TestComponent() {
|
437
437
|
render();
|
438
|
-
|
438
|
+
useCachedResponsivePrecommitValue(cache, hookOnCommit);
|
439
439
|
|
440
440
|
React.useEffect(() => {
|
441
441
|
componentCommits();
|
@@ -528,7 +528,7 @@ describe('useCachedPrecommitValue', () => {
|
|
528
528
|
function TestComponent() {
|
529
529
|
render();
|
530
530
|
|
531
|
-
|
531
|
+
useCachedResponsivePrecommitValue(cache, hookOnCommit);
|
532
532
|
|
533
533
|
React.useEffect(() => {
|
534
534
|
componentCommits();
|
@@ -1,15 +1,14 @@
|
|
1
1
|
'use strict';
|
2
2
|
|
3
|
-
import { useEffect, useState } from 'react';
|
4
|
-
import { ParentCache } from './ParentCache';
|
5
|
-
import { useHasCommittedRef } from './useHasCommittedRef';
|
6
3
|
import { ItemCleanupPair } from '@isograph/disposable-types';
|
4
|
+
import { useEffect, useRef, useState } from 'react';
|
5
|
+
import { ParentCache } from './ParentCache';
|
7
6
|
|
8
7
|
/**
|
9
|
-
*
|
8
|
+
* useCachedResponsivePrecommitValue<T>
|
10
9
|
* - Takes a mutable parent cache, a factory function, and an onCommit callback.
|
11
|
-
* - Returns T before
|
12
|
-
* - Calls onCommit with the ItemCleanupPair during
|
10
|
+
* - Returns T before commit after every parent cache change, and null afterward.
|
11
|
+
* - Calls onCommit with the ItemCleanupPair during commit after every parent cache change.
|
13
12
|
* - The T from the render phase is only temporarily retained. It may have been
|
14
13
|
* disposed by the time of the commit. If so, this hook checks the parent cache
|
15
14
|
* for another T or creates one, and passes this T to onCommit.
|
@@ -26,17 +25,18 @@ import { ItemCleanupPair } from '@isograph/disposable-types';
|
|
26
25
|
* with a set of variables (e.g. {name: "Matthew"}), and pass in another cache
|
27
26
|
* (e.g. associated with {name: "James"}), which is empty, the hook will fill that
|
28
27
|
* new cache with the factory function.
|
28
|
+
* - Post-commit, passing a different parentCache will reset hook to the pre-commit
|
29
|
+
* state. The cache will return T before commit, then fill the new cache with the
|
30
|
+
* factory function and return null afterwards.
|
29
31
|
*
|
30
32
|
* Passing a different factory:
|
31
33
|
* - Passing a different factory has no effect, except when factory is called,
|
32
|
-
* which is when the parent cache is being filled, or during
|
34
|
+
* which is when the parent cache is being filled, or during commit.
|
33
35
|
*
|
34
36
|
* Passing a different onCommit:
|
35
|
-
* - Passing a different onCommit has no effect, except for during
|
36
|
-
*
|
37
|
-
* Post-commit, all parameters are ignored and the hook returns null.
|
37
|
+
* - Passing a different onCommit has no effect, except for during commit.
|
38
38
|
*/
|
39
|
-
export function
|
39
|
+
export function useCachedResponsivePrecommitValue<T>(
|
40
40
|
parentCache: ParentCache<T>,
|
41
41
|
onCommit: (pair: ItemCleanupPair<T>) => void,
|
42
42
|
): { state: T } | null {
|
@@ -47,9 +47,11 @@ export function useCachedPrecommitValue<T>(
|
|
47
47
|
//
|
48
48
|
// This hook is the former, i.e. re-renders if the committed item has changed.
|
49
49
|
const [, rerender] = useState<{} | null>(null);
|
50
|
+
const lastCommittedParentCache = useRef<ParentCache<T> | null>(null);
|
50
51
|
|
51
52
|
useEffect(() => {
|
52
|
-
|
53
|
+
lastCommittedParentCache.current = parentCache;
|
54
|
+
// On commit, cacheItem may be disposed, because during the render phase,
|
53
55
|
// we only temporarily retained the item, and the temporary retain could have
|
54
56
|
// expired by the time of the commit.
|
55
57
|
//
|
@@ -81,21 +83,19 @@ export function useCachedPrecommitValue<T>(
|
|
81
83
|
// We did not find an item in the parent cache, create a new one.
|
82
84
|
onCommit(parentCache.factory());
|
83
85
|
}
|
84
|
-
|
85
86
|
// TODO: Consider whether we always want to rerender if the committed item
|
86
87
|
// was not returned during the last render, or whether some callers will
|
87
88
|
// prefer opting out of this behavior (e.g. if every disposable item behaves
|
88
89
|
// identically, but must be loaded.)
|
89
90
|
rerender({});
|
90
91
|
}
|
91
|
-
}, []);
|
92
|
+
}, [parentCache]);
|
92
93
|
|
93
|
-
|
94
|
-
if (hasCommittedRef.current) {
|
94
|
+
if (lastCommittedParentCache.current === parentCache) {
|
95
95
|
return null;
|
96
96
|
}
|
97
97
|
|
98
|
-
// Safety: item is only safe to use (i.e. guaranteed not to have disposed)
|
98
|
+
// Safety: item is only safe to use (i.e. guaranteed not to have been disposed)
|
99
99
|
// during this tick.
|
100
100
|
const [cacheItem, item, disposeOfTemporaryRetain] =
|
101
101
|
parentCache.getOrPopulateAndTemporaryRetain();
|
@@ -1,7 +1,7 @@
|
|
1
|
+
import { ItemCleanupPair } from '@isograph/disposable-types';
|
1
2
|
import { useEffect, useRef } from 'react';
|
2
3
|
import { ParentCache } from './ParentCache';
|
3
|
-
import {
|
4
|
-
import { useCachedPrecommitValue } from './useCachedPrecommitValue';
|
4
|
+
import { useCachedResponsivePrecommitValue } from './useCachedResponsivePrecommitValue';
|
5
5
|
import {
|
6
6
|
UNASSIGNED_STATE,
|
7
7
|
UnassignedState,
|
@@ -18,9 +18,13 @@ export function useDisposableState<T = never>(
|
|
18
18
|
): UseUpdatableDisposableStateReturnValue<T> {
|
19
19
|
const itemCleanupPairRef = useRef<ItemCleanupPair<T> | null>(null);
|
20
20
|
|
21
|
-
const preCommitItem =
|
22
|
-
|
23
|
-
|
21
|
+
const preCommitItem = useCachedResponsivePrecommitValue(
|
22
|
+
parentCache,
|
23
|
+
(pair) => {
|
24
|
+
itemCleanupPairRef.current?.[1]();
|
25
|
+
itemCleanupPairRef.current = pair;
|
26
|
+
},
|
27
|
+
);
|
24
28
|
|
25
29
|
const { state: stateFromDisposableStateHook, setState } =
|
26
30
|
useUpdatableDisposableState<T>();
|
@@ -68,8 +72,8 @@ export function useDisposableState<T = never>(
|
|
68
72
|
(stateFromDisposableStateHook != UNASSIGNED_STATE
|
69
73
|
? stateFromDisposableStateHook
|
70
74
|
: null) ??
|
71
|
-
|
72
|
-
|
75
|
+
preCommitItem?.state ??
|
76
|
+
itemCleanupPairRef.current?.[0];
|
73
77
|
|
74
78
|
return {
|
75
79
|
state: state!,
|
@@ -0,0 +1,71 @@
|
|
1
|
+
import { ItemCleanupPair } from '@isograph/disposable-types';
|
2
|
+
import React, { useEffect, useState } from 'react';
|
3
|
+
import { create } from 'react-test-renderer';
|
4
|
+
import { describe, expect, test, vi } from 'vitest';
|
5
|
+
import { ParentCache } from './ParentCache';
|
6
|
+
import { useLazyDisposableState } from './useLazyDisposableState';
|
7
|
+
|
8
|
+
function createCache<T>(value: T) {
|
9
|
+
const disposeItem = vi.fn();
|
10
|
+
const factory = vi.fn(() => {
|
11
|
+
const pair: ItemCleanupPair<T> = [value, disposeItem];
|
12
|
+
return pair;
|
13
|
+
});
|
14
|
+
const cache = new ParentCache(factory);
|
15
|
+
return { cache, disposeItem };
|
16
|
+
}
|
17
|
+
|
18
|
+
function promiseWithResolvers() {
|
19
|
+
let resolve;
|
20
|
+
let reject;
|
21
|
+
const promise = new Promise((_resolve, _reject) => {
|
22
|
+
resolve = _resolve;
|
23
|
+
reject = _reject;
|
24
|
+
});
|
25
|
+
return { resolve, reject, promise };
|
26
|
+
}
|
27
|
+
|
28
|
+
describe('useLazyDisposableState', async () => {
|
29
|
+
test('on cache change, it should dispose previous cache', async () => {
|
30
|
+
const cache1 = createCache(1);
|
31
|
+
const cache2 = createCache(2);
|
32
|
+
const renders = vi.fn();
|
33
|
+
|
34
|
+
let unmounted = promiseWithResolvers();
|
35
|
+
let committed = promiseWithResolvers();
|
36
|
+
|
37
|
+
function TestComponent() {
|
38
|
+
const [cache, setCache] = useState(cache1.cache);
|
39
|
+
const { state } = useLazyDisposableState(cache);
|
40
|
+
|
41
|
+
useEffect(() => {
|
42
|
+
setCache(cache2.cache);
|
43
|
+
|
44
|
+
return () => {
|
45
|
+
unmounted.resolve();
|
46
|
+
};
|
47
|
+
}, []);
|
48
|
+
|
49
|
+
useEffect(() => {
|
50
|
+
if (state == 1) return;
|
51
|
+
committed.resolve();
|
52
|
+
}, [state]);
|
53
|
+
|
54
|
+
renders(state);
|
55
|
+
|
56
|
+
return null;
|
57
|
+
}
|
58
|
+
|
59
|
+
const root = create(<TestComponent />, { unstable_isConcurrent: true });
|
60
|
+
await committed.promise;
|
61
|
+
expect(cache1.disposeItem).toHaveBeenCalled();
|
62
|
+
expect(cache1.cache.factory).toHaveBeenCalledOnce();
|
63
|
+
root.unmount();
|
64
|
+
await unmounted.promise;
|
65
|
+
expect(cache2.disposeItem).toHaveBeenCalled();
|
66
|
+
expect(cache2.cache.factory).toHaveBeenCalledOnce();
|
67
|
+
expect(renders).toHaveBeenNthCalledWith(1, 1);
|
68
|
+
expect(renders).toHaveBeenNthCalledWith(2, 2);
|
69
|
+
expect(renders).toHaveBeenCalledTimes(2);
|
70
|
+
});
|
71
|
+
});
|
@@ -1,40 +1,50 @@
|
|
1
1
|
'use strict';
|
2
2
|
|
3
|
+
import type { ItemCleanupPair } from '@isograph/isograph-disposable-types';
|
3
4
|
import { useEffect, useRef } from 'react';
|
4
|
-
import type { ItemCleanupPair } from '@isograph/disposable-types';
|
5
5
|
import { ParentCache } from './ParentCache';
|
6
|
-
import {
|
6
|
+
import { useCachedResponsivePrecommitValue } from './useCachedResponsivePrecommitValue';
|
7
|
+
import { type UnassignedState } from './useUpdatableDisposableState';
|
7
8
|
|
8
9
|
/**
|
9
10
|
* useLazyDisposableState<T>
|
10
11
|
* - Takes a mutable parent cache and a factory function
|
11
12
|
* - Returns { state: T }
|
12
13
|
*
|
13
|
-
* This lazily loads the disposable item using
|
14
|
+
* This lazily loads the disposable item using useCachedResponsivePrecommitValue, then
|
14
15
|
* (on commit) sets it in state. The item continues to be returned after
|
15
16
|
* commit and is disposed when the hook unmounts.
|
16
17
|
*/
|
17
|
-
export function useLazyDisposableState<T>(
|
18
|
+
export function useLazyDisposableState<T>(
|
19
|
+
parentCache: ParentCache<Exclude<T, UnassignedState>>,
|
20
|
+
): {
|
18
21
|
state: T;
|
19
22
|
} {
|
20
23
|
const itemCleanupPairRef = useRef<ItemCleanupPair<T> | null>(null);
|
21
24
|
|
22
|
-
const preCommitItem =
|
23
|
-
|
24
|
-
|
25
|
+
const preCommitItem = useCachedResponsivePrecommitValue(
|
26
|
+
parentCache,
|
27
|
+
(pair) => {
|
28
|
+
itemCleanupPairRef.current?.[1]();
|
29
|
+
itemCleanupPairRef.current = pair;
|
30
|
+
},
|
31
|
+
);
|
25
32
|
|
26
33
|
useEffect(() => {
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
34
|
+
return () => {
|
35
|
+
const cleanupFn = itemCleanupPairRef.current?.[1];
|
36
|
+
// TODO confirm useEffect is called in order.
|
37
|
+
if (cleanupFn == null) {
|
38
|
+
throw new Error(
|
39
|
+
'cleanupFn unexpectedly null. This indicates a bug in react-disposable-state.',
|
40
|
+
);
|
41
|
+
}
|
42
|
+
return cleanupFn();
|
43
|
+
};
|
35
44
|
}, []);
|
36
45
|
|
37
46
|
const returnedItem = preCommitItem?.state ?? itemCleanupPairRef.current?.[0];
|
47
|
+
|
38
48
|
if (returnedItem != null) {
|
39
49
|
return { state: returnedItem };
|
40
50
|
}
|
@@ -1,9 +1,9 @@
|
|
1
|
-
import { describe, test, vi, expect } from 'vitest';
|
2
1
|
import React from 'react';
|
3
2
|
import { create } from 'react-test-renderer';
|
3
|
+
import { describe, expect, test, vi } from 'vitest';
|
4
4
|
import {
|
5
|
-
useUpdatableDisposableState,
|
6
5
|
UNASSIGNED_STATE,
|
6
|
+
useUpdatableDisposableState,
|
7
7
|
} from './useUpdatableDisposableState';
|
8
8
|
|
9
9
|
function Suspender({ promise, isResolvedRef }) {
|
package/tsconfig.json
CHANGED