@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.
Files changed (39) hide show
  1. package/.turbo/turbo-compile-typescript.log +4 -0
  2. package/README.md +9 -15
  3. package/dist/CacheItem.d.ts +1 -0
  4. package/dist/CacheItem.d.ts.map +1 -0
  5. package/dist/CacheItem.js +2 -2
  6. package/dist/ParentCache.d.ts +3 -2
  7. package/dist/ParentCache.d.ts.map +1 -0
  8. package/dist/ParentCache.js +1 -1
  9. package/dist/index.d.ts +2 -1
  10. package/dist/index.d.ts.map +1 -0
  11. package/dist/index.js +1 -1
  12. package/dist/{useCachedPrecommitValue.d.ts → useCachedResponsivePrecommitValue.d.ts} +11 -9
  13. package/dist/useCachedResponsivePrecommitValue.d.ts.map +1 -0
  14. package/dist/{useCachedPrecommitValue.js → useCachedResponsivePrecommitValue.js} +16 -16
  15. package/dist/useDisposableState.d.ts +2 -1
  16. package/dist/useDisposableState.d.ts.map +1 -0
  17. package/dist/useDisposableState.js +7 -6
  18. package/dist/useHasCommittedRef.d.ts +1 -0
  19. package/dist/useHasCommittedRef.d.ts.map +1 -0
  20. package/dist/useHasCommittedRef.js +1 -2
  21. package/dist/useLazyDisposableState.d.ts +4 -2
  22. package/dist/useLazyDisposableState.d.ts.map +1 -0
  23. package/dist/useLazyDisposableState.js +15 -12
  24. package/dist/useUpdatableDisposableState.d.ts +1 -0
  25. package/dist/useUpdatableDisposableState.d.ts.map +1 -0
  26. package/dist/useUpdatableDisposableState.js +2 -2
  27. package/docs/managing-complex-state.md +32 -28
  28. package/package.json +9 -9
  29. package/src/CacheItem.test.ts +5 -5
  30. package/src/ParentCache.test.ts +2 -2
  31. package/src/ParentCache.ts +2 -2
  32. package/src/index.ts +1 -1
  33. package/src/{useCachedPrecommitValue.test.tsx → useCachedResponsivePrecommitValue.test.tsx} +12 -12
  34. package/src/{useCachedPrecommitValue.ts → useCachedResponsivePrecommitValue.ts} +17 -17
  35. package/src/useDisposableState.ts +11 -7
  36. package/src/useLazyDisposableState.test.tsx +71 -0
  37. package/src/useLazyDisposableState.ts +25 -15
  38. package/src/useUpdatableDisposableState.test.tsx +2 -2
  39. package/tsconfig.json +2 -2
@@ -0,0 +1,4 @@
1
+
2
+ > @isograph/react-disposable-state@0.3.1 compile-typescript /Users/runner/work/isograph/isograph/libs/isograph-react-disposable-state
3
+ > rm -rf dist/* && tsc -p tsconfig.pkg.json
4
+
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: ParentCache<T>,
88
- factory: Loader<T>,
89
- options: ?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
@@ -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.createTemporarilyRetainedCacheItem = exports.CacheItem = void 0;
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;
@@ -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 useCachedPrecommitValue, when the parent cache is populated
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"}
@@ -28,7 +28,7 @@ class ParentCache {
28
28
  this.__factory = factory;
29
29
  }
30
30
  /**
31
- * This is called from useCachedPrecommitValue, when the parent cache is populated
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 './useCachedPrecommitValue';
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("./useCachedPrecommitValue"), exports);
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
- * usePrecommitValue<T>
4
+ * useCachedResponsivePrecommitValue<T>
5
5
  * - Takes a mutable parent cache, a factory function, and an onCommit callback.
6
- * - Returns T before the initial commit, and null afterward.
7
- * - Calls onCommit with the ItemCleanupPair during the first commit.
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 the initial commit.
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 the initial commit.
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 useCachedPrecommitValue<T>(parentCache: ParentCache<T>, onCommit: (pair: ItemCleanupPair<T>) => void): {
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.useCachedPrecommitValue = void 0;
3
+ exports.useCachedResponsivePrecommitValue = useCachedResponsivePrecommitValue;
4
4
  const react_1 = require("react");
5
- const useHasCommittedRef_1 = require("./useHasCommittedRef");
6
5
  /**
7
- * usePrecommitValue<T>
6
+ * useCachedResponsivePrecommitValue<T>
8
7
  * - Takes a mutable parent cache, a factory function, and an onCommit callback.
9
- * - Returns T before the initial commit, and null afterward.
10
- * - Calls onCommit with the ItemCleanupPair during the first commit.
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 the initial commit.
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 the initial commit.
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 useCachedPrecommitValue(parentCache, onCommit) {
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
- // On first commit, cacheItem may be disposed, because during the render phase,
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
- const hasCommittedRef = (0, useHasCommittedRef_1.useHasCommittedRef)();
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 = void 0;
3
+ exports.useDisposableState = useDisposableState;
4
4
  const react_1 = require("react");
5
- const useCachedPrecommitValue_1 = require("./useCachedPrecommitValue");
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, useCachedPrecommitValue_1.useCachedPrecommitValue)(parentCache, (pair) => {
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 = (_c = (_a = (stateFromDisposableStateHook != useUpdatableDisposableState_1.UNASSIGNED_STATE
49
+ const state = (_b = (_a = (stateFromDisposableStateHook != useUpdatableDisposableState_1.UNASSIGNED_STATE
48
50
  ? stateFromDisposableStateHook
49
- : null)) !== null && _a !== void 0 ? _a : (_b = itemCleanupPairRef.current) === null || _b === void 0 ? void 0 : _b[0]) !== null && _c !== void 0 ? _c : preCommitItem === null || preCommitItem === void 0 ? void 0 : preCommitItem.state;
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;
@@ -3,3 +3,4 @@ import { MutableRefObject } from 'react';
3
3
  * Returns true if the component has committed, false otherwise.
4
4
  */
5
5
  export declare function useHasCommittedRef(): MutableRefObject<boolean>;
6
+ //# sourceMappingURL=useHasCommittedRef.d.ts.map
@@ -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 = void 0;
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 useCachedPrecommitValue, then
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 = void 0;
3
+ exports.useLazyDisposableState = useLazyDisposableState;
4
4
  const react_1 = require("react");
5
- const useCachedPrecommitValue_1 = require("./useCachedPrecommitValue");
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 useCachedPrecommitValue, then
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, useCachedPrecommitValue_1.useCachedPrecommitValue)(parentCache, (pair) => {
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
- var _a;
23
- const cleanupFn = (_a = itemCleanupPairRef.current) === null || _a === void 0 ? void 0 : _a[1];
24
- // TODO confirm useEffect is called in order.
25
- if (cleanupFn == null) {
26
- throw new Error('cleanupFn unexpectedly null. This indicates a bug in react-disposable-state.');
27
- }
28
- return cleanupFn;
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;
@@ -37,3 +37,4 @@ type UseUpdatableDisposableStateReturnValue<T> = {
37
37
  */
38
38
  export declare function useUpdatableDisposableState<T = never>(): UseUpdatableDisposableStateReturnValue<T>;
39
39
  export {};
40
+ //# sourceMappingURL=useUpdatableDisposableState.d.ts.map
@@ -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.useUpdatableDisposableState = exports.UNASSIGNED_STATE = void 0;
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 { state, setState } = useUpdatableDisposableState();
17
- // assume state === [item1], and the cleanup function will dispose item1
18
-
19
- const addItem2ToState = () => {
20
- setState([item1, item2], () => {
21
- disposeItem1();
22
- disposeItem2();
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 { state, setState } = useUpdatableDisposableState();
55
- // assume state === [item1activeReference], and the cleanup function will dispose that active reference
56
-
57
- const addItem2ToState = () => {
58
- const [item2, disposeItem2] = createDisposeItem2();
59
- const [item2ActiveReference, disposeItem2ActiveReference] =
60
- createReferenceCountedPointer([item2, disposeItem2]);
61
-
62
- // get a new active reference to the existing item1
63
- const [item1ActiveReference, disposeItem1ActiveReference] = nullthrows(
64
- state[0].cloneIfNotDisposed(),
65
- );
66
- setState([item1ActiveReference, item2ActiveReference], () => {
67
- disposeItem1ActiveReference();
68
- disposeItem2ActiveReference();
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.2.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": "yarn run compile",
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": "0.2.0"
22
+ "@isograph/disposable-types": "*"
22
23
  },
23
24
  "peerDependencies": {
24
- "react": "18.2.0"
25
+ "react": "^18.0.0 || ^19.0.0"
25
26
  },
26
27
  "devDependencies": {
27
- "@types/react": "^18.0.31",
28
+ "@types/react": "18.3.1",
28
29
  "react-test-renderer": "^18.2.0",
29
- "vitest": "^0.29.8",
30
- "typescript": "^5.0.3"
30
+ "typescript": "5.6.3"
31
31
  },
32
32
  "repository": {
33
33
  "type": "git",
@@ -1,18 +1,18 @@
1
+ import { ItemCleanupPair } from '@isograph/disposable-types';
1
2
  import {
2
- describe,
3
+ afterEach,
3
4
  assert,
4
- test,
5
- vi,
6
5
  beforeEach,
7
- afterEach,
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>;
@@ -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;
@@ -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 useCachedPrecommitValue, when the parent cache is populated
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 './useCachedPrecommitValue';
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('useCachedPrecommitValue', () => {
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 = useCachedPrecommitValue(cache, hookOnCommit);
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 = useCachedPrecommitValue(cache, hookOnCommit);
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 = useCachedPrecommitValue(cache, hookOnCommit);
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 = useCachedPrecommitValue(cache, hookOnCommit);
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 = useCachedPrecommitValue(cache, hookOnCommit);
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
- useCachedPrecommitValue(cache, hookOnCommit);
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
- useCachedPrecommitValue(cache, hookOnCommit);
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
- useCachedPrecommitValue(cache, hookOnCommit);
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
- * usePrecommitValue<T>
8
+ * useCachedResponsivePrecommitValue<T>
10
9
  * - Takes a mutable parent cache, a factory function, and an onCommit callback.
11
- * - Returns T before the initial commit, and null afterward.
12
- * - Calls onCommit with the ItemCleanupPair during the first commit.
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 the initial commit.
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 the initial commit.
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 useCachedPrecommitValue<T>(
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
- // On first commit, cacheItem may be disposed, because during the render phase,
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
- const hasCommittedRef = useHasCommittedRef();
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 { ItemCleanupPair } from '@isograph/disposable-types';
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 = useCachedPrecommitValue(parentCache, (pair) => {
22
- itemCleanupPairRef.current = pair;
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
- itemCleanupPairRef.current?.[0] ??
72
- preCommitItem?.state;
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 { useCachedPrecommitValue } from './useCachedPrecommitValue';
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 useCachedPrecommitValue, then
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>(parentCache: ParentCache<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 = useCachedPrecommitValue(parentCache, (pair) => {
23
- itemCleanupPairRef.current = pair;
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
- const cleanupFn = itemCleanupPairRef.current?.[1];
28
- // TODO confirm useEffect is called in order.
29
- if (cleanupFn == null) {
30
- throw new Error(
31
- 'cleanupFn unexpectedly null. This indicates a bug in react-disposable-state.',
32
- );
33
- }
34
- return cleanupFn;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "extends": "./tsconfig.pkg.json",
3
3
  "compilerOptions": {
4
- "noEmit": true,
5
- },
4
+ "noEmit": true
5
+ }
6
6
  }