@isograph/react-disposable-state 0.0.4 → 0.1.0

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.
@@ -1,13 +1,13 @@
1
- import { describe, test, vi, expect, assert } from "vitest";
2
- import { ParentCache } from "./ParentCache";
3
- import { ItemCleanupPair } from "@isograph/disposable-types";
4
- import { useCachedPrecommitValue } from "./useCachedPrecommitValue";
5
- import React from "react";
6
- import { create } from "react-test-renderer";
7
- import { CacheItem, CacheItemState } from "./CacheItem";
1
+ import { describe, test, vi, expect, assert } from 'vitest';
2
+ import { ParentCache } from './ParentCache';
3
+ import { ItemCleanupPair } from '@isograph/disposable-types';
4
+ import { useCachedPrecommitValue } from './useCachedPrecommitValue';
5
+ import React from 'react';
6
+ import { create } from 'react-test-renderer';
7
+ import { CacheItem, CacheItemState } from './CacheItem';
8
8
 
9
9
  function getItem<T>(cache: ParentCache<T>): CacheItem<T> | null {
10
- return (cache as any).__item;
10
+ return (cache as any).__cacheItem;
11
11
  }
12
12
 
13
13
  function getState<T>(cacheItem: CacheItem<T>): CacheItemState<T> {
@@ -54,14 +54,14 @@ function promiseAndResolver() {
54
54
  async function awaitableCreate(Component, isConcurrent) {
55
55
  const element = create(
56
56
  Component,
57
- isConcurrent ? { unstable_isConcurrent: true } : undefined
57
+ isConcurrent ? { unstable_isConcurrent: true } : undefined,
58
58
  );
59
59
  await shortPromise();
60
60
  return element;
61
61
  }
62
62
 
63
- describe("useCachedPrecommitValue", () => {
64
- test("on initial render, it should call getOrPopulateAndTemporaryRetain", async () => {
63
+ describe('useCachedPrecommitValue', () => {
64
+ test('on initial render, it should call getOrPopulateAndTemporaryRetain', async () => {
65
65
  const disposeItem = vi.fn();
66
66
  const factory = vi.fn(() => {
67
67
  const pair: ItemCleanupPair<number> = [1, disposeItem];
@@ -70,7 +70,7 @@ describe("useCachedPrecommitValue", () => {
70
70
  const cache = new ParentCache(factory);
71
71
  const getOrPopulateAndTemporaryRetain = vi.spyOn(
72
72
  cache,
73
- "getOrPopulateAndTemporaryRetain"
73
+ 'getOrPopulateAndTemporaryRetain',
74
74
  );
75
75
 
76
76
  const componentCommits = vi.fn();
@@ -103,7 +103,7 @@ describe("useCachedPrecommitValue", () => {
103
103
  expect(render).toHaveBeenCalledTimes(1);
104
104
  });
105
105
 
106
- test("on commit, it should call the provided callback and empty the parent cache", async () => {
106
+ test('on commit, it should call the provided callback and empty the parent cache', async () => {
107
107
  const disposeItem = vi.fn();
108
108
  const factory = vi.fn(() => {
109
109
  const pair: ItemCleanupPair<number> = [1, disposeItem];
@@ -124,7 +124,7 @@ describe("useCachedPrecommitValue", () => {
124
124
  expect(componentCommits).toHaveBeenCalledTimes(1);
125
125
  expect(hookOnCommit).toBeCalledTimes(1);
126
126
  expect(hookOnCommit.mock.calls[0][0][0]).toBe(1);
127
- expect(typeof hookOnCommit.mock.calls[0][0][1]).toBe("function");
127
+ expect(typeof hookOnCommit.mock.calls[0][0][1]).toBe('function');
128
128
  expect(factory).toBeCalledTimes(1);
129
129
  expect(disposeItem).not.toBeCalled();
130
130
  expect(cache.isEmpty()).toBe(true);
@@ -140,7 +140,7 @@ describe("useCachedPrecommitValue", () => {
140
140
  expect(render).toHaveBeenCalledTimes(1);
141
141
  });
142
142
 
143
- test("after commit, on subsequent renders it should return null", async () => {
143
+ test('after commit, on subsequent renders it should return null', async () => {
144
144
  const disposeItem = vi.fn();
145
145
  const factory = vi.fn(() => {
146
146
  const pair: ItemCleanupPair<number> = [1, disposeItem];
@@ -186,8 +186,8 @@ describe("useCachedPrecommitValue", () => {
186
186
  });
187
187
 
188
188
  test(
189
- "on repeated pre-commit renders, if the temporary retain is not disposed, " +
190
- "it should re-call getOrPopulateAndTemporaryRetain but not call factory again",
189
+ 'on repeated pre-commit renders, if the temporary retain is not disposed, ' +
190
+ 'it should re-call getOrPopulateAndTemporaryRetain but not call factory again',
191
191
  async () => {
192
192
  const disposeItem = vi.fn();
193
193
  const factory = vi.fn(() => {
@@ -197,7 +197,7 @@ describe("useCachedPrecommitValue", () => {
197
197
  const cache = new ParentCache(factory);
198
198
  const getOrPopulateAndTemporaryRetain = vi.spyOn(
199
199
  cache,
200
- "getOrPopulateAndTemporaryRetain"
200
+ 'getOrPopulateAndTemporaryRetain',
201
201
  );
202
202
 
203
203
  const componentCommits = vi.fn();
@@ -213,7 +213,7 @@ describe("useCachedPrecommitValue", () => {
213
213
 
214
214
  renderCount++;
215
215
  expect(getOrPopulateAndTemporaryRetain).toHaveBeenCalledTimes(
216
- renderCount
216
+ renderCount,
217
217
  );
218
218
 
219
219
  React.useEffect(() => {
@@ -233,7 +233,7 @@ describe("useCachedPrecommitValue", () => {
233
233
  <TestComponent />
234
234
  <Suspender promise={promise} isResolvedRef={isResolvedRef} />
235
235
  </React.Suspense>,
236
- true
236
+ true,
237
237
  );
238
238
 
239
239
  expect(componentCommits).toHaveBeenCalledTimes(0);
@@ -244,12 +244,12 @@ describe("useCachedPrecommitValue", () => {
244
244
 
245
245
  expect(componentCommits).toHaveBeenCalledTimes(1);
246
246
  expect(render).toHaveBeenCalledTimes(2);
247
- }
247
+ },
248
248
  );
249
249
 
250
250
  test(
251
- "on repeated pre-commit renders, if the temporary retain is disposed, " +
252
- "it should re-call getOrPopulateAndTemporaryRetain and factory",
251
+ 'on repeated pre-commit renders, if the temporary retain is disposed, ' +
252
+ 'it should re-call getOrPopulateAndTemporaryRetain and factory',
253
253
  async () => {
254
254
  const disposeItem = vi.fn();
255
255
  let factoryValue = 0;
@@ -262,7 +262,7 @@ describe("useCachedPrecommitValue", () => {
262
262
 
263
263
  const getOrPopulateAndTemporaryRetain = vi.spyOn(
264
264
  cache,
265
- "getOrPopulateAndTemporaryRetain"
265
+ 'getOrPopulateAndTemporaryRetain',
266
266
  );
267
267
 
268
268
  const componentCommits = vi.fn();
@@ -305,7 +305,7 @@ describe("useCachedPrecommitValue", () => {
305
305
  <TestComponent />
306
306
  <Suspender promise={promise} isResolvedRef={isResolvedRef} />
307
307
  </React.Suspense>,
308
- true
308
+ true,
309
309
  );
310
310
 
311
311
  expect(componentCommits).toHaveBeenCalledTimes(0);
@@ -316,13 +316,13 @@ describe("useCachedPrecommitValue", () => {
316
316
 
317
317
  expect(componentCommits).toHaveBeenCalledTimes(1);
318
318
  expect(render).toHaveBeenCalledTimes(2);
319
- }
319
+ },
320
320
  );
321
321
 
322
322
  test(
323
- "if the item has been disposed between the render and the commit, " +
324
- "and the parent cache is empty, it will call factory again, re-render an " +
325
- "additional time and called onCommit with the newly generated item",
323
+ 'if the item has been disposed between the render and the commit, ' +
324
+ 'and the parent cache is empty, it will call factory again, re-render an ' +
325
+ 'additional time and called onCommit with the newly generated item',
326
326
  async () => {
327
327
  const disposeItem = vi.fn();
328
328
  let factoryCount = 0;
@@ -334,11 +334,11 @@ describe("useCachedPrecommitValue", () => {
334
334
  const cache = new ParentCache(factory);
335
335
  const getOrPopulateAndTemporaryRetain = vi.spyOn(
336
336
  cache,
337
- "getOrPopulateAndTemporaryRetain"
337
+ 'getOrPopulateAndTemporaryRetain',
338
338
  );
339
339
  const getAndPermanentRetainIfPresent = vi.spyOn(
340
340
  cache,
341
- "getAndPermanentRetainIfPresent"
341
+ 'getAndPermanentRetainIfPresent',
342
342
  );
343
343
 
344
344
  const componentCommits = vi.fn();
@@ -354,7 +354,7 @@ describe("useCachedPrecommitValue", () => {
354
354
  expect(getOrPopulateAndTemporaryRetain).toHaveBeenCalledTimes(1);
355
355
  expect(getAndPermanentRetainIfPresent).toHaveBeenCalledTimes(1);
356
356
  expect(getAndPermanentRetainIfPresent.mock.results[0].value).toBe(
357
- null
357
+ null,
358
358
  );
359
359
  expect(factory).toHaveBeenCalledTimes(2);
360
360
  expect(cache.isEmpty()).toBe(true);
@@ -402,20 +402,20 @@ describe("useCachedPrecommitValue", () => {
402
402
  <TestComponent />
403
403
  <CodeExecutor />
404
404
  </>,
405
- false
405
+ false,
406
406
  );
407
407
 
408
408
  // This code executes after the commit and re-render of TestComponent.
409
409
  // The commit triggers a re-render, because the item was disposed.
410
410
  expect(render).toHaveBeenCalledTimes(2);
411
411
  expect(factory).toBeCalledTimes(2);
412
- }
412
+ },
413
413
  );
414
414
 
415
415
  test(
416
- "if, between the render and the commit, the item has been disposed, " +
417
- "and the parent cache is not empty, it will not call factory again, will re-render " +
418
- "an additional time and will call onCommit with the value in the parent cache",
416
+ 'if, between the render and the commit, the item has been disposed, ' +
417
+ 'and the parent cache is not empty, it will not call factory again, will re-render ' +
418
+ 'an additional time and will call onCommit with the value in the parent cache',
419
419
  async () => {
420
420
  const disposeItem = vi.fn();
421
421
  let factoryCount = 0;
@@ -427,7 +427,7 @@ describe("useCachedPrecommitValue", () => {
427
427
  const cache = new ParentCache(factory);
428
428
  const getAndPermanentRetainIfPresent = vi.spyOn(
429
429
  cache,
430
- "getAndPermanentRetainIfPresent"
430
+ 'getAndPermanentRetainIfPresent',
431
431
  );
432
432
 
433
433
  const componentCommits = vi.fn();
@@ -443,7 +443,7 @@ describe("useCachedPrecommitValue", () => {
443
443
  expect(getOrPopulateAndTemporaryRetain).toHaveBeenCalledTimes(2);
444
444
  expect(getAndPermanentRetainIfPresent).toHaveBeenCalledTimes(1);
445
445
  expect(getAndPermanentRetainIfPresent.mock.results[0].value[0]).toBe(
446
- 2
446
+ 2,
447
447
  );
448
448
  expect(factory).toHaveBeenCalledTimes(2);
449
449
  expect(hookOnCommit).toHaveBeenCalledTimes(1);
@@ -455,7 +455,7 @@ describe("useCachedPrecommitValue", () => {
455
455
 
456
456
  const getOrPopulateAndTemporaryRetain = vi.spyOn(
457
457
  cache,
458
- "getOrPopulateAndTemporaryRetain"
458
+ 'getOrPopulateAndTemporaryRetain',
459
459
  );
460
460
 
461
461
  // wat is going on?
@@ -499,7 +499,7 @@ describe("useCachedPrecommitValue", () => {
499
499
  <TestComponent />
500
500
  <CodeExecutor />
501
501
  </React.Suspense>,
502
- false
502
+ false,
503
503
  );
504
504
 
505
505
  // This code executes after the commit and re-render of TestComponent.
@@ -508,13 +508,12 @@ describe("useCachedPrecommitValue", () => {
508
508
  // Note that this is the same number of calls as inside of CodeExecutor,
509
509
  // implying that the factory function was not called again.
510
510
  expect(factory).toBeCalledTimes(2);
511
- }
511
+ },
512
512
  );
513
513
 
514
514
  test(
515
- "If the component unmounts before committing, " +
516
- "the item will remain in the parent cache, " +
517
- "temporarily retained",
515
+ 'After render but before commit, the item will ' +
516
+ 'be in the parent cache, temporarily retained',
518
517
  async () => {
519
518
  const disposeItem = vi.fn();
520
519
  const factory = vi.fn(() => {
@@ -541,36 +540,21 @@ describe("useCachedPrecommitValue", () => {
541
540
  // wat is going on?
542
541
  //
543
542
  // We want to test a scenario where the component unmounts before committing.
543
+ // However, we cannot distinguish between an unmount before commit and a
544
+ // render and a commit that hasn't happened yet.
544
545
  //
545
- // The subcomponents are rendered in order: TestComponent followed by CodeExecutor.
546
- // So, during CodeExecutor, we trigger a state update that causes the ParentComponent
547
- // to not render the children.
548
- function CodeExecutor() {
549
- setShowChildren(false);
550
- return null;
551
- }
552
-
553
- let setShowChildren;
554
- function ParentComponent({ children }) {
555
- const [showChildren, _setShowChildren] = React.useState(true);
556
- setShowChildren = _setShowChildren;
557
-
558
- if (showChildren) {
559
- return children;
560
- } else {
561
- return null;
562
- }
563
- }
546
+ // This can be simulated with suspense.
547
+ //
548
+ // This test and 'on initial render, it should call getOrPopulateAndTemporaryRetain'
549
+ // can be merged
564
550
 
551
+ const { promise, isResolvedRef } = promiseAndResolver();
565
552
  const element = await awaitableCreate(
566
- <ParentComponent>
553
+ <React.Suspense fallback={null}>
567
554
  <TestComponent />
568
- <CodeExecutor />
569
- </ParentComponent>,
570
- // If we're not in concurrent mode, TestComponent will mount before
571
- // unmounting. This perhaps is a bug in react-test-renderer. Regardless,
572
- // we're not interested in that scenario.
573
- true
555
+ <Suspender promise={promise} isResolvedRef={isResolvedRef} />
556
+ </React.Suspense>,
557
+ true,
574
558
  );
575
559
 
576
560
  // This code executes after the commit and re-render of TestComponent.
@@ -579,9 +563,9 @@ describe("useCachedPrecommitValue", () => {
579
563
  expect(componentCommits).toHaveBeenCalledTimes(0);
580
564
  const item = getItem(cache)!;
581
565
  const state = getState(item);
582
- assert(state.kind === "InParentCacheAndNotDisposed");
566
+ assert(state.kind === 'InParentCacheAndNotDisposed');
583
567
  expect(state.permanentRetainCount).toBe(0);
584
568
  expect(state.temporaryRetainCount).toBe(1);
585
- }
569
+ },
586
570
  );
587
571
  });
@@ -1,9 +1,9 @@
1
- "use strict";
1
+ 'use strict';
2
2
 
3
- import { useEffect, useState } from "react";
4
- import { ParentCache } from "./ParentCache";
5
- import { useHasCommittedRef } from "./useHasCommittedRef";
6
- import { ItemCleanupPair } from "@isograph/isograph-disposable-types/dist";
3
+ import { useEffect, useState } from 'react';
4
+ import { ParentCache } from './ParentCache';
5
+ import { useHasCommittedRef } from './useHasCommittedRef';
6
+ import { ItemCleanupPair } from '@isograph/isograph-disposable-types/dist';
7
7
 
8
8
  /**
9
9
  * usePrecommitValue<T>
@@ -38,7 +38,7 @@ import { ItemCleanupPair } from "@isograph/isograph-disposable-types/dist";
38
38
  */
39
39
  export function useCachedPrecommitValue<T>(
40
40
  parentCache: ParentCache<T>,
41
- onCommit: (pair: ItemCleanupPair<T>) => void
41
+ onCommit: (pair: ItemCleanupPair<T>) => void,
42
42
  ): { state: T } | null {
43
43
  // TODO: there should be two APIs. One in which we always re-render if the
44
44
  // committed item was not returned during the last render, and one in which
@@ -66,7 +66,7 @@ export function useCachedPrecommitValue<T>(
66
66
  // After the above, we have a non-disposed item and a cleanup function, which we
67
67
  // can pass to onCommit.
68
68
  const undisposedPair = cacheItem.permanentRetainIfNotDisposed(
69
- disposeOfTemporaryRetain
69
+ disposeOfTemporaryRetain,
70
70
  );
71
71
  if (undisposedPair !== null) {
72
72
  onCommit(undisposedPair);
@@ -1,12 +1,12 @@
1
- import { useEffect, useRef } from "react";
2
- import { ParentCache } from "./ParentCache";
3
- import { ItemCleanupPair } from "@isograph/disposable-types";
4
- import { useCachedPrecommitValue } from "./useCachedPrecommitValue";
1
+ import { useEffect, useRef } from 'react';
2
+ import { ParentCache } from './ParentCache';
3
+ import { ItemCleanupPair } from '@isograph/disposable-types';
4
+ import { useCachedPrecommitValue } from './useCachedPrecommitValue';
5
5
  import {
6
6
  UNASSIGNED_STATE,
7
7
  UnassignedState,
8
8
  useUpdatableDisposableState,
9
- } from "./useUpdatableDisposableState";
9
+ } from './useUpdatableDisposableState';
10
10
 
11
11
  type UseUpdatableDisposableStateReturnValue<T> = {
12
12
  state: T;
@@ -14,7 +14,7 @@ type UseUpdatableDisposableStateReturnValue<T> = {
14
14
  };
15
15
 
16
16
  export function useDisposableState<T = never>(
17
- parentCache: ParentCache<T>
17
+ parentCache: ParentCache<T>,
18
18
  ): UseUpdatableDisposableStateReturnValue<T> {
19
19
  const itemCleanupPairRef = useRef<ItemCleanupPair<T> | null>(null);
20
20
 
@@ -33,13 +33,13 @@ export function useDisposableState<T = never>(
33
33
  itemCleanupPairRef.current = null;
34
34
  } else {
35
35
  throw new Error(
36
- "itemCleanupPairRef.current is unexpectedly null. " +
37
- "This indicates a bug in react-disposable-state."
36
+ 'itemCleanupPairRef.current is unexpectedly null. ' +
37
+ 'This indicates a bug in react-disposable-state.',
38
38
  );
39
39
  }
40
40
  }
41
41
  },
42
- [stateFromDisposableStateHook]
42
+ [stateFromDisposableStateHook],
43
43
  );
44
44
 
45
45
  useEffect(function cleanupItemCleanupPairRefIfSetStateNotCalled() {
@@ -81,12 +81,14 @@ export function useDisposableState<T = never>(
81
81
  function tsTests() {
82
82
  let x: any;
83
83
  const a = useDisposableState(x);
84
+ // This should be a compiler error, because the generic is inferred to be of
85
+ // type never. TODO determine why this doesn't break the build!
84
86
  // @ts-expect-error
85
- a.setState(["asdf", () => {}]);
87
+ a.setState(['asdf', () => {}]);
86
88
  // @ts-expect-error
87
89
  a.setState([UNASSIGNED_STATE, () => {}]);
88
90
  const b = useDisposableState<string | UnassignedState>(x);
89
91
  // @ts-expect-error
90
92
  b.setState([UNASSIGNED_STATE, () => {}]);
91
- b.setState(["asdf", () => {}]);
93
+ b.setState(['asdf', () => {}]);
92
94
  }
@@ -1,4 +1,4 @@
1
- import { MutableRefObject, useEffect, useRef } from "react";
1
+ import { MutableRefObject, useEffect, useRef } from 'react';
2
2
 
3
3
  /**
4
4
  * Returns true if the component has committed, false otherwise.
@@ -1,9 +1,9 @@
1
- "use strict";
1
+ 'use strict';
2
2
 
3
- import { useEffect, useRef } from "react";
4
- import type { ItemCleanupPair } from "@isograph/disposable-types";
5
- import { ParentCache } from "./ParentCache";
6
- import { useCachedPrecommitValue } from "./useCachedPrecommitValue";
3
+ import { useEffect, useRef } from 'react';
4
+ import type { ItemCleanupPair } from '@isograph/disposable-types';
5
+ import { ParentCache } from './ParentCache';
6
+ import { useCachedPrecommitValue } from './useCachedPrecommitValue';
7
7
 
8
8
  /**
9
9
  * useLazyDisposableState<T>
@@ -28,7 +28,7 @@ export function useLazyDisposableState<T>(parentCache: ParentCache<T>): {
28
28
  // TODO confirm useEffect is called in order.
29
29
  if (cleanupFn == null) {
30
30
  throw new Error(
31
- "cleanupFn unexpectedly null. This indicates a bug in react-disposable-state."
31
+ 'cleanupFn unexpectedly null. This indicates a bug in react-disposable-state.',
32
32
  );
33
33
  }
34
34
  return cleanupFn;
@@ -43,6 +43,6 @@ export function useLazyDisposableState<T>(parentCache: ParentCache<T>): {
43
43
  // is non-null. During the initial commit, we assign itemCleanupPairRef.current,
44
44
  // so during subsequent renders, itemCleanupPairRef.current is non-null.
45
45
  throw new Error(
46
- "returnedItem was unexpectedly null. This indicates a bug in react-disposable-state."
46
+ 'returnedItem was unexpectedly null. This indicates a bug in react-disposable-state.',
47
47
  );
48
48
  }