@assistant-ui/tap 0.6.1 → 0.7.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 (213) hide show
  1. package/README.md +9 -6
  2. package/dist/core/ResourceFiber.d.ts +5 -5
  3. package/dist/core/ResourceFiber.d.ts.map +1 -1
  4. package/dist/core/ResourceFiber.js +26 -18
  5. package/dist/core/ResourceFiber.js.map +1 -1
  6. package/dist/core/createTapRoot.d.ts +9 -0
  7. package/dist/core/createTapRoot.d.ts.map +1 -0
  8. package/dist/core/createTapRoot.js +27 -0
  9. package/dist/core/createTapRoot.js.map +1 -0
  10. package/dist/core/helpers/commit.d.ts +1 -1
  11. package/dist/core/helpers/commit.d.ts.map +1 -1
  12. package/dist/core/helpers/commit.js +6 -1
  13. package/dist/core/helpers/commit.js.map +1 -1
  14. package/dist/core/helpers/execution-context.d.ts +4 -5
  15. package/dist/core/helpers/execution-context.d.ts.map +1 -1
  16. package/dist/core/helpers/execution-context.js +1 -7
  17. package/dist/core/helpers/execution-context.js.map +1 -1
  18. package/dist/core/helpers/root.d.ts +3 -2
  19. package/dist/core/helpers/root.d.ts.map +1 -1
  20. package/dist/core/helpers/root.js +19 -15
  21. package/dist/core/helpers/root.js.map +1 -1
  22. package/dist/core/react-dispatcher.d.ts.map +1 -1
  23. package/dist/core/react-dispatcher.js +14 -14
  24. package/dist/core/react-dispatcher.js.map +1 -1
  25. package/dist/core/resource.d.ts +2 -4
  26. package/dist/core/resource.d.ts.map +1 -1
  27. package/dist/core/resource.js +5 -10
  28. package/dist/core/resource.js.map +1 -1
  29. package/dist/core/scheduler.d.ts +2 -2
  30. package/dist/core/scheduler.d.ts.map +1 -1
  31. package/dist/core/scheduler.js +2 -2
  32. package/dist/core/scheduler.js.map +1 -1
  33. package/dist/core/types.d.ts +27 -25
  34. package/dist/core/types.d.ts.map +1 -1
  35. package/dist/hooks/useResource.d.ts +2 -2
  36. package/dist/hooks/useResource.d.ts.map +1 -1
  37. package/dist/hooks/useResource.js +14 -20
  38. package/dist/hooks/useResource.js.map +1 -1
  39. package/dist/hooks/useResources.d.ts +1 -1
  40. package/dist/hooks/useResources.d.ts.map +1 -1
  41. package/dist/hooks/useResources.js +18 -27
  42. package/dist/hooks/useResources.js.map +1 -1
  43. package/dist/hooks/useTapHost.d.ts +21 -0
  44. package/dist/hooks/useTapHost.d.ts.map +1 -0
  45. package/dist/hooks/useTapHost.js +30 -0
  46. package/dist/hooks/useTapHost.js.map +1 -0
  47. package/dist/hooks/useTapRoot.d.ts +18 -0
  48. package/dist/hooks/useTapRoot.d.ts.map +1 -0
  49. package/dist/hooks/useTapRoot.js +77 -0
  50. package/dist/hooks/useTapRoot.js.map +1 -0
  51. package/dist/hooks/utils/depsShallowEqual.d.ts.map +1 -1
  52. package/dist/hooks/utils/depsShallowEqual.js +5 -2
  53. package/dist/hooks/utils/depsShallowEqual.js.map +1 -1
  54. package/dist/hooks/utils/useCell.d.ts +2 -2
  55. package/dist/hooks/utils/useCell.d.ts.map +1 -1
  56. package/dist/hooks/utils/useCell.js.map +1 -1
  57. package/dist/hooks/utils/useDevStrictMode.d.ts +5 -0
  58. package/dist/hooks/utils/useDevStrictMode.d.ts.map +1 -0
  59. package/dist/hooks/utils/useDevStrictMode.js +25 -0
  60. package/dist/hooks/utils/useDevStrictMode.js.map +1 -0
  61. package/dist/hooks/utils/useRenderMemo.d.ts +5 -0
  62. package/dist/hooks/utils/useRenderMemo.d.ts.map +1 -0
  63. package/dist/hooks/utils/useRenderMemo.js +25 -0
  64. package/dist/hooks/utils/useRenderMemo.js.map +1 -0
  65. package/dist/hooks/utils/useResourceFiberHostUtils.d.ts +10 -0
  66. package/dist/hooks/utils/useResourceFiberHostUtils.d.ts.map +1 -0
  67. package/dist/hooks/utils/useResourceFiberHostUtils.js +46 -0
  68. package/dist/hooks/utils/useResourceFiberHostUtils.js.map +1 -0
  69. package/dist/index.d.ts +7 -4
  70. package/dist/index.js +7 -4
  71. package/dist/{hooks → react-hooks}/index.d.ts +6 -6
  72. package/dist/{hooks → react-hooks}/index.js +5 -5
  73. package/dist/{hooks → react-hooks}/use.d.ts +1 -1
  74. package/dist/{hooks → react-hooks}/use.d.ts.map +1 -1
  75. package/dist/{hooks → react-hooks}/use.js +1 -1
  76. package/dist/react-hooks/use.js.map +1 -0
  77. package/dist/{hooks → react-hooks}/useCallback.d.ts +1 -1
  78. package/dist/react-hooks/useCallback.d.ts.map +1 -0
  79. package/dist/{hooks → react-hooks}/useCallback.js +1 -1
  80. package/dist/react-hooks/useCallback.js.map +1 -0
  81. package/dist/{hooks → react-hooks}/useEffect.d.ts +1 -1
  82. package/dist/react-hooks/useEffect.d.ts.map +1 -0
  83. package/dist/react-hooks/useEffect.js +35 -0
  84. package/dist/react-hooks/useEffect.js.map +1 -0
  85. package/dist/{hooks → react-hooks}/useEffectEvent.d.ts +1 -1
  86. package/dist/react-hooks/useEffectEvent.d.ts.map +1 -0
  87. package/dist/{hooks → react-hooks}/useEffectEvent.js +2 -2
  88. package/dist/react-hooks/useEffectEvent.js.map +1 -0
  89. package/dist/{hooks → react-hooks}/useMemo.d.ts +1 -1
  90. package/dist/react-hooks/useMemo.d.ts.map +1 -0
  91. package/dist/{hooks → react-hooks}/useMemo.js +3 -3
  92. package/dist/react-hooks/useMemo.js.map +1 -0
  93. package/dist/{hooks → react-hooks}/useMemoCache.d.ts +1 -1
  94. package/dist/react-hooks/useMemoCache.d.ts.map +1 -0
  95. package/dist/{hooks → react-hooks}/useMemoCache.js +1 -1
  96. package/dist/react-hooks/useMemoCache.js.map +1 -0
  97. package/dist/react-hooks/useReducer.d.ts +9 -0
  98. package/dist/react-hooks/useReducer.d.ts.map +1 -0
  99. package/dist/react-hooks/useReducer.js +120 -0
  100. package/dist/react-hooks/useReducer.js.map +1 -0
  101. package/dist/{hooks → react-hooks}/useRef.d.ts +1 -1
  102. package/dist/react-hooks/useRef.d.ts.map +1 -0
  103. package/dist/{hooks → react-hooks}/useRef.js +1 -1
  104. package/dist/react-hooks/useRef.js.map +1 -0
  105. package/dist/{hooks → react-hooks}/useState.d.ts +1 -1
  106. package/dist/react-hooks/useState.d.ts.map +1 -0
  107. package/dist/{hooks → react-hooks}/useState.js +3 -3
  108. package/dist/react-hooks/useState.js.map +1 -0
  109. package/dist/react-shim/index.js +11 -11
  110. package/dist/react-shim/index.js.map +1 -1
  111. package/package.json +1 -1
  112. package/src/__tests__/basic/resourceHandle.test.ts +32 -22
  113. package/src/__tests__/basic/tapEffect.basic.test.ts +8 -8
  114. package/src/__tests__/basic/tapReducer.basic.test.ts +16 -14
  115. package/src/__tests__/basic/tapResources.basic.test.ts +19 -16
  116. package/src/__tests__/basic/tapState.basic.test.ts +11 -11
  117. package/src/__tests__/bench/hosts.bench.tsx +124 -0
  118. package/src/__tests__/bench/tree.bench.tsx +166 -0
  119. package/src/__tests__/errors/errors.effect-errors.test.ts +12 -13
  120. package/src/__tests__/errors/errors.render-errors.test.ts +65 -22
  121. package/src/__tests__/lifecycle/lifecycle.dependencies.test.ts +19 -19
  122. package/src/__tests__/lifecycle/lifecycle.mount-unmount.test.ts +14 -14
  123. package/src/__tests__/parity/describeParity.tsx +217 -0
  124. package/src/__tests__/parity/parity.adversarial.test.tsx +375 -0
  125. package/src/__tests__/parity/parity.basics.test.tsx +281 -0
  126. package/src/__tests__/parity/parity.divergences.test.tsx +208 -0
  127. package/src/__tests__/parity/parity.smoke.test.tsx +43 -0
  128. package/src/__tests__/react/concurrent-mode.test.tsx +10 -6
  129. package/src/__tests__/react/concurrent-pending-updates.test.tsx +351 -0
  130. package/src/__tests__/react/concurrent-render-phase.test.tsx +350 -0
  131. package/src/__tests__/react/react-shim.test.tsx +1 -1
  132. package/src/__tests__/react/useResource.test.tsx +41 -26
  133. package/src/__tests__/react/useTapHost.test.tsx +233 -0
  134. package/src/__tests__/react-dispatcher.test.ts +4 -4
  135. package/src/__tests__/rules/rules.hook-count.test.ts +21 -21
  136. package/src/__tests__/rules/rules.hook-order.test.ts +17 -17
  137. package/src/__tests__/strictmode/strictmode-parity.test.tsx +420 -0
  138. package/src/__tests__/strictmode/strictmode.test.ts +39 -209
  139. package/src/__tests__/test-utils.ts +33 -23
  140. package/src/core/ResourceFiber.ts +43 -35
  141. package/src/core/createTapRoot.ts +45 -0
  142. package/src/core/helpers/commit.ts +12 -2
  143. package/src/core/helpers/execution-context.ts +4 -13
  144. package/src/core/helpers/root.ts +24 -12
  145. package/src/core/react-dispatcher.ts +10 -9
  146. package/src/core/resource.ts +5 -20
  147. package/src/core/scheduler.ts +1 -1
  148. package/src/core/types.ts +27 -21
  149. package/src/hooks/useResource.ts +18 -27
  150. package/src/hooks/useResources.ts +18 -42
  151. package/src/hooks/useTapHost.ts +60 -0
  152. package/src/hooks/useTapRoot.ts +135 -0
  153. package/src/hooks/utils/depsShallowEqual.ts +12 -2
  154. package/src/hooks/utils/useCell.ts +2 -2
  155. package/src/hooks/utils/useDevStrictMode.ts +34 -0
  156. package/src/hooks/utils/useRenderMemo.ts +27 -0
  157. package/src/hooks/utils/useResourceFiberHostUtils.ts +61 -0
  158. package/src/index.ts +6 -3
  159. package/src/{hooks → react-hooks}/index.ts +4 -4
  160. package/src/react-hooks/useEffect.ts +58 -0
  161. package/src/{hooks → react-hooks}/useMemo.ts +1 -1
  162. package/src/react-hooks/useReducer.ts +254 -0
  163. package/src/{hooks → react-hooks}/useState.ts +2 -2
  164. package/src/react-shim/index.ts +1 -1
  165. package/dist/core/createResourceRoot.d.ts +0 -11
  166. package/dist/core/createResourceRoot.d.ts.map +0 -1
  167. package/dist/core/createResourceRoot.js +0 -31
  168. package/dist/core/createResourceRoot.js.map +0 -1
  169. package/dist/core/helpers/callResourceFn.d.ts +0 -1
  170. package/dist/core/helpers/callResourceFn.js +0 -19
  171. package/dist/core/helpers/callResourceFn.js.map +0 -1
  172. package/dist/hooks/use.js.map +0 -1
  173. package/dist/hooks/useCallback.d.ts.map +0 -1
  174. package/dist/hooks/useCallback.js.map +0 -1
  175. package/dist/hooks/useEffect.d.ts.map +0 -1
  176. package/dist/hooks/useEffect.js +0 -40
  177. package/dist/hooks/useEffect.js.map +0 -1
  178. package/dist/hooks/useEffectEvent.d.ts.map +0 -1
  179. package/dist/hooks/useEffectEvent.js.map +0 -1
  180. package/dist/hooks/useMemo.d.ts.map +0 -1
  181. package/dist/hooks/useMemo.js.map +0 -1
  182. package/dist/hooks/useMemoCache.d.ts.map +0 -1
  183. package/dist/hooks/useMemoCache.js.map +0 -1
  184. package/dist/hooks/useReducer.d.ts +0 -21
  185. package/dist/hooks/useReducer.d.ts.map +0 -1
  186. package/dist/hooks/useReducer.js +0 -81
  187. package/dist/hooks/useReducer.js.map +0 -1
  188. package/dist/hooks/useRef.d.ts.map +0 -1
  189. package/dist/hooks/useRef.js.map +0 -1
  190. package/dist/hooks/useResourceRoot.d.ts +0 -20
  191. package/dist/hooks/useResourceRoot.d.ts.map +0 -1
  192. package/dist/hooks/useResourceRoot.js +0 -77
  193. package/dist/hooks/useResourceRoot.js.map +0 -1
  194. package/dist/hooks/useState.d.ts.map +0 -1
  195. package/dist/hooks/useState.js.map +0 -1
  196. package/dist/react/hooks.d.ts +0 -25
  197. package/dist/react/hooks.d.ts.map +0 -1
  198. package/dist/react/hooks.js +0 -69
  199. package/dist/react/hooks.js.map +0 -1
  200. package/src/__tests__/strictmode/react-strictmode-behavior.test.tsx +0 -920
  201. package/src/__tests__/strictmode/react-strictmode-rerender-sources.test.tsx +0 -488
  202. package/src/__tests__/strictmode/tap-strictmode-rerender-sources.test.ts +0 -687
  203. package/src/core/createResourceRoot.ts +0 -53
  204. package/src/core/helpers/callResourceFn.ts +0 -21
  205. package/src/hooks/useEffect.ts +0 -72
  206. package/src/hooks/useReducer.ts +0 -160
  207. package/src/hooks/useResourceRoot.ts +0 -130
  208. package/src/react/hooks.ts +0 -112
  209. /package/src/{hooks → react-hooks}/use.ts +0 -0
  210. /package/src/{hooks → react-hooks}/useCallback.ts +0 -0
  211. /package/src/{hooks → react-hooks}/useEffectEvent.ts +0 -0
  212. /package/src/{hooks → react-hooks}/useMemoCache.ts +0 -0
  213. /package/src/{hooks → react-hooks}/useRef.ts +0 -0
@@ -1,53 +0,0 @@
1
- import type { ResourceElement } from "./types";
2
- import {
3
- createResourceFiber,
4
- unmountResourceFiber,
5
- renderResourceFiber,
6
- commitResourceFiber,
7
- } from "./ResourceFiber";
8
- import { useResourceRoot } from "../hooks/useResourceRoot";
9
- import { resource } from "./resource";
10
- import { isDevelopment } from "./helpers/env";
11
- import { flushResourcesSync, UpdateScheduler } from "./scheduler";
12
- import { createResourceFiberRoot } from "./helpers/root";
13
-
14
- const SubscribableResource = resource(useResourceRoot);
15
-
16
- export const createResourceRoot = () => {
17
- const fiber = createResourceFiber<
18
- useResourceRoot.SubscribableResource<any>,
19
- ResourceElement<any>
20
- >(
21
- SubscribableResource,
22
- createResourceFiberRoot((callback) => {
23
- new UpdateScheduler(() => {
24
- if (callback()) {
25
- throw new Error(
26
- "Unexpected rerender of createResourceRoot outer fiber",
27
- );
28
- }
29
- return false;
30
- }).markDirty();
31
- }),
32
- undefined,
33
- isDevelopment ? "root" : null,
34
- );
35
-
36
- return {
37
- render: <R, P>(element: ResourceElement<R, P>) => {
38
- // In strict mode, render twice to detect side effects
39
- if (isDevelopment && fiber.devStrictMode === "root") {
40
- void renderResourceFiber(fiber, element);
41
- }
42
-
43
- const render = renderResourceFiber(fiber, element);
44
-
45
- flushResourcesSync(() => commitResourceFiber(fiber, render));
46
-
47
- return render.output as useResourceRoot.SubscribableResource<R>;
48
- },
49
- unmount: () => {
50
- unmountResourceFiber(fiber);
51
- },
52
- };
53
- };
@@ -1,21 +0,0 @@
1
- import type { Resource } from "../types";
2
-
3
- /**
4
- * Renders a resource with the given props.
5
- * @internal This is for internal use only.
6
- */
7
- export function callResourceFn<R, P>(resource: Resource<R, P>, props: P): R {
8
- const fn = (resource as unknown as { [fnSymbol]?: (props: P) => R })[
9
- fnSymbol
10
- ];
11
- if (!fn) {
12
- throw new Error("ResourceElement.type is not a valid Resource");
13
- }
14
- return fn(props);
15
- }
16
-
17
- /**
18
- * Symbol used to store the ResourceFn in the Resource constructor.
19
- * @internal This is for internal use only.
20
- */
21
- export const fnSymbol = Symbol("fnSymbol");
@@ -1,72 +0,0 @@
1
- import type { Cell } from "../core/types";
2
- import { depsShallowEqual } from "./utils/depsShallowEqual";
3
- import { useCell, registerRenderMountTask } from "./utils/useCell";
4
-
5
- const newEffect = (): Cell & { type: "effect" } => ({
6
- type: "effect",
7
- cleanup: undefined,
8
- deps: null, // null means the effect has never been run
9
- });
10
-
11
- export namespace useEffect {
12
- export type Destructor = () => void;
13
- export type EffectCallback = () => Destructor | undefined;
14
- }
15
-
16
- export function useEffect(effect: useEffect.EffectCallback): void;
17
- export function useEffect(
18
- effect: useEffect.EffectCallback,
19
- deps: readonly unknown[],
20
- ): void;
21
- export function useEffect(
22
- effect: useEffect.EffectCallback,
23
- deps?: readonly unknown[],
24
- ): void {
25
- const cell = useCell("effect", newEffect);
26
-
27
- if (deps && cell.deps && depsShallowEqual(cell.deps, deps)) return;
28
- if (cell.deps !== null && !!deps !== !!cell.deps)
29
- throw new Error(
30
- "useEffect called with and without dependencies across re-renders",
31
- );
32
-
33
- registerRenderMountTask(() => {
34
- const errors: unknown[] = [];
35
-
36
- try {
37
- cell.cleanup?.();
38
- } catch (error) {
39
- errors.push(error);
40
- } finally {
41
- cell.cleanup = undefined;
42
- }
43
-
44
- try {
45
- const cleanup = effect();
46
-
47
- if (cleanup !== undefined && typeof cleanup !== "function") {
48
- throw new Error(
49
- "An effect function must either return a cleanup function or nothing. " +
50
- `Received: ${typeof cleanup}`,
51
- );
52
- }
53
-
54
- cell.cleanup = cleanup;
55
- } catch (error) {
56
- errors.push(error);
57
- }
58
-
59
- cell.deps = deps;
60
-
61
- if (errors.length > 0) {
62
- if (errors.length === 1) {
63
- throw errors[0];
64
- } else {
65
- for (const error of errors) {
66
- console.error(error);
67
- }
68
- throw new AggregateError(errors, "Errors during commit");
69
- }
70
- }
71
- });
72
- }
@@ -1,160 +0,0 @@
1
- import { isDevelopment } from "../core/helpers/env";
2
- import { getCurrentResourceFiber } from "../core/helpers/execution-context";
3
- import type { ReducerQueueEntry, ResourceFiber } from "../core/types";
4
- import { markCellDirty } from "../core/helpers/root";
5
- import { useCell } from "./utils/useCell";
6
-
7
- type Dispatch<A> = (action: A) => void;
8
-
9
- const dispatchOnFiber = (
10
- fiber: ResourceFiber<any, any>,
11
- callback: () => (() => void) | null,
12
- ): void => {
13
- if (fiber.renderContext) {
14
- throw new Error("Resource updated during render");
15
- }
16
- if (fiber.isNeverMounted) {
17
- throw new Error("Resource updated before mount");
18
- }
19
-
20
- fiber.root.dispatchUpdate(() => {
21
- const result = callback();
22
- if (result) {
23
- result();
24
- fiber.root.changelog.push(result);
25
- return true;
26
- }
27
- return false;
28
- });
29
- };
30
-
31
- function useReducerImpl<S, A, I, R extends S>(
32
- reducer: (state: S, action: A) => S,
33
- getDerivedState: ((state: S) => R) | undefined,
34
- initialArg: S | I,
35
- initFn: ((arg: I) => S) | undefined,
36
- ): [R, Dispatch<A>] {
37
- const cell = useCell("reducer", () => {
38
- const fiber = getCurrentResourceFiber();
39
-
40
- // First render: compute initial state
41
- const initialState = initFn ? initFn(initialArg as I) : initialArg;
42
-
43
- if (isDevelopment && fiber.devStrictMode && initFn) {
44
- void initFn(initialArg as I);
45
- }
46
-
47
- return {
48
- type: "reducer",
49
- queue: new Set(),
50
- dirty: false,
51
- workInProgress: initialState,
52
- current: initialState,
53
- reducer,
54
- dispatch: (action: A) => {
55
- const entry: ReducerQueueEntry = {
56
- action,
57
- hasEagerState: false,
58
- eagerState: undefined,
59
- };
60
-
61
- dispatchOnFiber(fiber, () => {
62
- if (fiber.root.dirtyCells.length === 0 && !entry.hasEagerState) {
63
- entry.eagerState = reducer(cell.workInProgress, action);
64
- entry.hasEagerState = true;
65
-
66
- if (Object.is(cell.current, entry.eagerState)) return null;
67
- }
68
-
69
- return () => {
70
- markCellDirty(fiber, cell);
71
- cell.queue.add(entry);
72
- };
73
- });
74
- },
75
- };
76
- });
77
-
78
- const fiber = getCurrentResourceFiber();
79
- const sameReducer = reducer === cell.reducer;
80
- cell.reducer = reducer;
81
-
82
- for (const item of cell.queue) {
83
- if (!item.hasEagerState || !sameReducer) {
84
- item.eagerState = reducer(cell.workInProgress, item.action);
85
- item.hasEagerState = true;
86
- }
87
-
88
- if (isDevelopment && fiber.devStrictMode) {
89
- void reducer(cell.workInProgress, item.action);
90
- }
91
-
92
- cell.workInProgress = item.eagerState;
93
- }
94
- cell.queue.clear();
95
-
96
- if (getDerivedState) {
97
- const derived = getDerivedState(cell.workInProgress);
98
-
99
- if (!Object.is(derived, cell.workInProgress)) {
100
- markCellDirty(fiber, cell);
101
- cell.workInProgress = derived;
102
- }
103
- }
104
-
105
- return [cell.workInProgress, cell.dispatch];
106
- }
107
-
108
- export function useReducer<S, A>(
109
- reducer: (state: S, action: A) => S,
110
- initialState: S,
111
- ): [S, Dispatch<A>];
112
- export function useReducer<S, A, I>(
113
- reducer: (state: S, action: A) => S,
114
- initialArg: I,
115
- init: (arg: I) => S,
116
- ): [S, Dispatch<A>];
117
- export function useReducer<S, A, I>(
118
- reducer: (state: S, action: A) => S,
119
- initialArg: S | I,
120
- init?: (arg: I) => S,
121
- ): [S, Dispatch<A>] {
122
- return useReducerImpl(
123
- reducer,
124
- undefined,
125
- initialArg as S,
126
- init as ((arg: S) => S) | undefined,
127
- );
128
- }
129
-
130
- /**
131
- * @deprecated experimental — a `getDerivedStateFromProps` replacement for
132
- * resources: adjust state in response to props without setting during render.
133
- * Tap-only for now (call it inside a resource render, not a React component) and
134
- * may change before stabilizing.
135
- */
136
- export function useReducerWithDerivedState<S, A, R extends S>(
137
- reducer: (state: S, action: A) => S,
138
- getDerivedState: (state: S) => R,
139
- initialState: S,
140
- ): [R, Dispatch<A>];
141
- /**
142
- * @deprecated experimental — a `getDerivedStateFromProps` replacement for
143
- * resources: adjust state in response to props without setting during render.
144
- * Tap-only for now (call it inside a resource render, not a React component) and
145
- * may change before stabilizing.
146
- */
147
- export function useReducerWithDerivedState<S, A, I, R extends S>(
148
- reducer: (state: S, action: A) => S,
149
- getDerivedState: (state: S) => R,
150
- initialArg: I,
151
- init: (arg: I) => S,
152
- ): [R, Dispatch<A>];
153
- export function useReducerWithDerivedState<S, A, I, R extends S>(
154
- reducer: (state: S, action: A) => S,
155
- getDerivedState: (state: S) => R,
156
- initialArg: I,
157
- init?: (arg: I) => S,
158
- ): [R, Dispatch<A>] {
159
- return useReducerImpl(reducer, getDerivedState, initialArg, init);
160
- }
@@ -1,130 +0,0 @@
1
- import {
2
- commitResourceFiber,
3
- createResourceFiber,
4
- renderResourceFiber,
5
- unmountResourceFiber,
6
- } from "../core/ResourceFiber";
7
- import { UpdateScheduler } from "../core/scheduler";
8
- import { useMemo } from "./useMemo";
9
- import { useEffect } from "./useEffect";
10
- import { useEffectEvent } from "./useEffectEvent";
11
- import { useRef } from "./useRef";
12
- import type { RenderResult, ResourceElement } from "../core/types";
13
- import { isDevelopment } from "../core/helpers/env";
14
- import {
15
- commitRoot,
16
- createResourceFiberRoot,
17
- setRootVersion,
18
- } from "../core/helpers/root";
19
-
20
- export namespace useResourceRoot {
21
- export type Unsubscribe = () => void;
22
-
23
- export interface SubscribableResource<TState> {
24
- /**
25
- * Get the current state of the store.
26
- */
27
- getValue(): TState;
28
-
29
- /**
30
- * Subscribe to the store.
31
- */
32
- subscribe(listener: () => void): Unsubscribe;
33
- }
34
- }
35
-
36
- // The root is never reset, because rollbacks are not supported in useResourceRoot.
37
-
38
- export const useResourceRoot = <TState>(
39
- element: ResourceElement<TState>,
40
- ): useResourceRoot.SubscribableResource<TState> => {
41
- const scheduler = useMemo(
42
- () => new UpdateScheduler(() => handleUpdate(null)),
43
- [],
44
- );
45
- const queue = useMemo(() => [] as (() => void)[], []);
46
-
47
- const fiber = useMemo(() => {
48
- void element.key;
49
-
50
- return createResourceFiber(
51
- element.type,
52
- createResourceFiberRoot((callback) => {
53
- if (!scheduler.isDirty && !callback()) return;
54
- queue.push(callback);
55
- scheduler.markDirty();
56
- }),
57
- );
58
- }, [element.type, element.key, queue, scheduler]);
59
-
60
- setRootVersion(fiber.root, fiber.root.committedVersion);
61
- const render = renderResourceFiber(fiber, element.props);
62
-
63
- const isMountedRef = useRef(false);
64
- const committedPropsRef = useRef(element.props);
65
- const valueRef = useRef<TState>(render.output);
66
- const subscribers = useMemo(() => new Set<() => void>(), []);
67
- const handleUpdate = useEffectEvent((render: RenderResult | null) => {
68
- if (render === null) {
69
- setRootVersion(fiber.root, 2);
70
- setRootVersion(fiber.root, 1);
71
-
72
- queue.forEach((callback) => {
73
- if (isDevelopment && fiber.devStrictMode) {
74
- callback();
75
- }
76
-
77
- callback();
78
- });
79
-
80
- if (isDevelopment && fiber.devStrictMode) {
81
- void renderResourceFiber(fiber, committedPropsRef.current);
82
- }
83
-
84
- render = renderResourceFiber(fiber, committedPropsRef.current);
85
- }
86
-
87
- if (scheduler.isDirty)
88
- throw new Error("Scheduler is dirty, this should never happen");
89
-
90
- commitRoot(fiber.root);
91
- queue.length = 0;
92
-
93
- if (isMountedRef.current) {
94
- commitResourceFiber(fiber, render);
95
- }
96
-
97
- if (scheduler.isDirty || valueRef.current === render.output) return;
98
- valueRef.current = render.output;
99
- subscribers.forEach((callback) => callback());
100
- });
101
-
102
- useEffect(() => {
103
- isMountedRef.current = true;
104
- return () => {
105
- isMountedRef.current = false;
106
- unmountResourceFiber(fiber);
107
- };
108
- }, [fiber]);
109
-
110
- useEffect(() => {
111
- committedPropsRef.current = render.props;
112
- commitRoot(fiber.root);
113
- commitResourceFiber(fiber, render);
114
-
115
- if (scheduler.isDirty || valueRef.current === render.output) return;
116
- valueRef.current = render.output;
117
- subscribers.forEach((callback) => callback());
118
- });
119
-
120
- return useMemo(
121
- () => ({
122
- getValue: () => valueRef.current,
123
- subscribe: (listener: () => void) => {
124
- subscribers.add(listener);
125
- return () => subscribers.delete(listener);
126
- },
127
- }),
128
- [subscribers],
129
- );
130
- };
@@ -1,112 +0,0 @@
1
- import { useLayoutEffect, useMemo, useReducer, useRef, useState } from "react";
2
- import type { ResourceFiberRoot, Resource } from "../core/types";
3
- import {
4
- createResourceFiber,
5
- unmountResourceFiber,
6
- renderResourceFiber,
7
- commitResourceFiber,
8
- } from "../core/ResourceFiber";
9
- import { isDevelopment } from "../core/helpers/env";
10
- import {
11
- commitRoot,
12
- createResourceFiberRoot,
13
- setRootVersion,
14
- } from "../core/helpers/root";
15
- import { peekResourceFiber } from "../core/helpers/execution-context";
16
- import * as hooks from "../hooks";
17
- import { resource } from "../core/resource";
18
-
19
- const useDevStrictMode = () => {
20
- if (!isDevelopment) return null;
21
-
22
- // oxlint-disable-next-line react/rules-of-hooks -- isDevelopment is a build-time constant, so this branch is stable per build
23
- const count = useRef(0);
24
- const isFirstRender = count.current === 0;
25
- // oxlint-disable-next-line react/rules-of-hooks -- isDevelopment is a build-time constant, so this branch is stable per build
26
- useState(() => count.current++);
27
- if (count.current !== 2) return null;
28
- return isFirstRender ? ("child" as const) : ("root" as const);
29
- };
30
-
31
- const HostResource = resource(function HostResource<T>(callback: () => T) {
32
- return callback();
33
- });
34
-
35
- // Runs `callback` inside a resource render hosted by a React component, so the
36
- // resource composition hooks (useResource/useResources/useResourceRoot) work from
37
- // React. `callback` executes inside the resource fiber below, so it may only call
38
- // resource hooks, not React's own hooks (which would have no fiber to attach to).
39
- // This is the single React->resource bridge; the React branch of every public
40
- // hook goes through it.
41
- const useResourceHost = <T>(callback: () => T): T => {
42
- const root = useMemo<ResourceFiberRoot>(() => {
43
- return createResourceFiberRoot((cb) => dispatch(cb));
44
- }, []);
45
-
46
- const [version, dispatch] = useReducer((v: number, cb: () => boolean) => {
47
- setRootVersion(root, v);
48
- return v + (cb() ? 1 : 0);
49
- }, 0);
50
- setRootVersion(root, version);
51
-
52
- const devStrictMode = useDevStrictMode();
53
- const fiber = useMemo(() => {
54
- return createResourceFiber(
55
- HostResource as unknown as Resource<T, () => T>,
56
- root,
57
- undefined,
58
- devStrictMode,
59
- );
60
- }, [root, devStrictMode]);
61
-
62
- const result = renderResourceFiber(fiber, callback);
63
- useLayoutEffect(() => {
64
- return () => unmountResourceFiber(fiber);
65
- }, [fiber]);
66
- useLayoutEffect(() => {
67
- commitRoot(root);
68
- commitResourceFiber(fiber, result);
69
- });
70
-
71
- return result.output;
72
- };
73
-
74
- // Turns a resource hook into an isomorphic hook with the SAME type (overloads
75
- // included): inside a resource render it calls the hook directly; inside a React
76
- // component it hosts it via the `useResourceHost` bridge. peekResourceFiber() is
77
- // stable per call site (a given call always renders in the same environment), so
78
- // the branch order across renders is fixed even though rules-of-hooks can't prove
79
- // it.
80
- const makeHook = <F extends (...args: any[]) => any>(hook: F): F =>
81
- ((...args: any[]) => {
82
- /* oxlint-disable react/rules-of-hooks */
83
- if (peekResourceFiber()) return hook(...args);
84
- return useResourceHost(() => hook(...args));
85
- /* oxlint-enable react/rules-of-hooks */
86
- }) as F;
87
-
88
- /**
89
- * Hosts a resource element. Inside a resource render it hosts the element as a
90
- * child resource; inside a React component it hosts it via the React bridge.
91
- * `propsDeps` is a resource-render optimization and is ignored on the React side.
92
- */
93
- export const useResource: typeof hooks.useResource = makeHook(
94
- hooks.useResource,
95
- );
96
-
97
- /**
98
- * Hosts a keyed list of resource elements. Inside a resource render it composes
99
- * them directly; inside a React component it hosts them via the React bridge.
100
- */
101
- export const useResources: typeof hooks.useResources = makeHook(
102
- hooks.useResources,
103
- );
104
-
105
- /**
106
- * Hosts a resource element as a subscribable root. Inside a resource render it
107
- * uses the root hook directly; inside a React component it hosts that root via
108
- * the React bridge (host it in one place; observe it via getValue/subscribe).
109
- */
110
- export const useResourceRoot: typeof hooks.useResourceRoot = makeHook(
111
- hooks.useResourceRoot,
112
- );
File without changes
File without changes
File without changes
File without changes
File without changes