bippy 0.7.3-dev.a6ed02c → 0.7.3-dev.e2b879b

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/react.ts CHANGED
@@ -5,6 +5,11 @@ import type { Fiber } from "./react-internals/index.js";
5
5
 
6
6
  export type { Fiber } from "./react-internals/index.js";
7
7
 
8
+ interface FiberHookEffect {
9
+ create: unknown;
10
+ next: unknown;
11
+ }
12
+
8
13
  const preserveState = (state: undefined): undefined => state;
9
14
  const readEmptySnapshot = (): undefined => undefined;
10
15
  const unsubscribeFromEmptyStore = (): void => {};
@@ -53,24 +58,40 @@ const useReducerCapture = (): void => {
53
58
  const useFiberWithExternalStore = (): Fiber | undefined =>
54
59
  captureFiberFromHook(useExternalStoreCapture) ?? undefined;
55
60
 
56
- const useFiberWithReducer = (): Fiber | undefined => {
57
- const committedFiberRef = React.useRef<Fiber | null>(null);
58
- const renderedFiberRef = React.useRef<Fiber | null>(null);
59
- const hookFiber = captureFiberFromHook(useReducerCapture);
60
- const fiber =
61
- hookFiber ??
62
- (renderedFiberRef.current !== committedFiberRef.current
63
- ? renderedFiberRef.current
64
- : committedFiberRef.current?.alternate) ??
65
- committedFiberRef.current;
66
-
67
- renderedFiberRef.current = fiber;
61
+ const isHookEffect = (value: unknown): value is FiberHookEffect =>
62
+ typeof value === "object" && value !== null && "create" in value && "next" in value;
68
63
 
69
- React.useEffect(() => {
70
- committedFiberRef.current = fiber;
71
- }, [fiber]);
64
+ const hasRenderMarker = (fiber: Fiber, renderMarker: () => void): boolean => {
65
+ const lastEffect = fiber.updateQueue?.lastEffect;
66
+ if (!isHookEffect(lastEffect)) return false;
67
+ let effect = lastEffect;
68
+ do {
69
+ if (effect.create === renderMarker) return true;
70
+ if (!isHookEffect(effect.next)) return false;
71
+ effect = effect.next;
72
+ } while (effect !== lastEffect);
73
+ return false;
74
+ };
72
75
 
73
- return fiber ?? undefined;
76
+ // HACK: React 17 only binds the rendering Fiber on mount. Later renders are identified through
77
+ // the effect list instead: renderWithHooks clears `updateQueue` on the work-in-progress Fiber,
78
+ // so only the Fiber that is rendering right now holds this render's marker effect.
79
+ const useFiberWithReducer = (): Fiber | undefined => {
80
+ const fiberRef = React.useRef<Fiber | null>(null);
81
+ const renderMarker = (): void => {};
82
+ React.useEffect(renderMarker, []);
83
+ const mountedFiber = captureFiberFromHook(useReducerCapture);
84
+ if (mountedFiber) {
85
+ fiberRef.current = mountedFiber;
86
+ return mountedFiber;
87
+ }
88
+ const knownFiber = fiberRef.current;
89
+ if (!knownFiber) return undefined;
90
+ return (
91
+ [knownFiber, knownFiber.alternate].find(
92
+ (candidate) => candidate !== null && hasRenderMarker(candidate, renderMarker),
93
+ ) ?? undefined
94
+ );
74
95
  };
75
96
 
76
97
  export const useFiber =
@@ -87,6 +87,16 @@ let currentHook: MemoizedState | null = null;
87
87
  let currentContextDependency: ContextDependency<unknown> | null = null;
88
88
  let currentThenableIndex = 0;
89
89
  let currentThenableState: unknown[] | null = null;
90
+ let currentMemoCacheIndex = 0;
91
+ let isInspectingHooks = false;
92
+
93
+ const assertNotInspectingHooks = (): void => {
94
+ if (isInspectingHooks) {
95
+ throw new BippyHookInspectionError(
96
+ "Hook inspection cannot be called during another inspection.",
97
+ );
98
+ }
99
+ };
90
100
 
91
101
  const SuspenseException: unknown = new Error(
92
102
  "Suspense interrupted this render. This error is an internal implementation detail of `use`.",
@@ -323,16 +333,8 @@ const dispatcherUseMemoCache = (size: number): unknown[] => {
323
333
  const memoCache = fiber.updateQueue?.memoCache;
324
334
  if (memoCache === null || memoCache === undefined) return [];
325
335
 
326
- let memoCacheSlots = memoCache.data[memoCache.index];
327
- if (memoCacheSlots === undefined) {
328
- memoCacheSlots = memoCache.data[memoCache.index] = Array.from(
329
- { length: size },
330
- () => REACT_MEMO_CACHE_SENTINEL,
331
- );
332
- }
333
-
334
- memoCache.index++;
335
- return memoCacheSlots;
336
+ const memoCacheSlots = memoCache.data[currentMemoCacheIndex++];
337
+ return memoCacheSlots?.slice() ?? Array.from({ length: size }, () => REACT_MEMO_CACHE_SENTINEL);
336
338
  };
337
339
 
338
340
  const dispatcherUseOptimistic = (passthrough: unknown): [unknown, () => void] => {
@@ -834,6 +836,7 @@ const performDispatcherInspection = <TArgs extends unknown[]>(
834
836
  ): HooksTree => {
835
837
  const previousDispatcher = readDispatcher(dispatcherRef);
836
838
  writeDispatcher(dispatcherRef, dispatcherProxy);
839
+ isInspectingHooks = true;
837
840
 
838
841
  let capturedHookLog: HookLogEntry[] = [];
839
842
  let ancestorStackError: Error | undefined;
@@ -846,6 +849,7 @@ const performDispatcherInspection = <TArgs extends unknown[]>(
846
849
  } finally {
847
850
  capturedHookLog = hookLog;
848
851
  hookLog = [];
852
+ isInspectingHooks = false;
849
853
  writeDispatcher(dispatcherRef, previousDispatcher);
850
854
  }
851
855
 
@@ -938,6 +942,7 @@ export const inspectHooks = (
938
942
  props: Record<string, unknown>,
939
943
  target: ReactDevToolsTarget = globalThis,
940
944
  ): HooksTree => {
945
+ assertNotInspectingHooks();
941
946
  const dispatcherRef = requireDispatcherRef(undefined, target);
942
947
  getPrimitiveStackCache();
943
948
  currentHook = null;
@@ -961,6 +966,7 @@ export const inspectHooks = (
961
966
  };
962
967
 
963
968
  export const getFiberHooks = (fiber: Fiber): HooksTree => {
969
+ assertNotInspectingHooks();
964
970
  const dispatcherRef = requireDispatcherRef(fiber);
965
971
  const workTags = getReactWorkTagsForFiber(fiber);
966
972
 
@@ -976,6 +982,7 @@ export const getFiberHooks = (fiber: Fiber): HooksTree => {
976
982
 
977
983
  currentHook = fiber.memoizedState;
978
984
  currentFiber = fiber;
985
+ currentMemoCacheIndex = 0;
979
986
 
980
987
  const debugThenableState = fiber.dependencies?._debugThenableState;
981
988
  const usedThenables = Array.isArray(debugThenableState)
@@ -984,18 +991,16 @@ export const getFiberHooks = (fiber: Fiber): HooksTree => {
984
991
  currentThenableState = Array.isArray(usedThenables) ? usedThenables : null;
985
992
  currentThenableIndex = 0;
986
993
 
987
- resolveContextDependency(fiber);
988
-
989
- const type = fiber.type;
990
- let props = fiber.memoizedProps;
991
- if (type !== fiber.elementType) {
992
- props = resolveDefaultProps(type, props);
993
- }
994
-
995
994
  const originalConsoleMethods = suppressConsole();
996
995
  const contextMap = new Map<ReactContext<unknown>, unknown>();
997
996
 
998
997
  try {
998
+ resolveContextDependency(fiber);
999
+ const type = fiber.type;
1000
+ const props =
1001
+ type !== fiber.elementType
1002
+ ? resolveDefaultProps(type, fiber.memoizedProps)
1003
+ : fiber.memoizedProps;
999
1004
  if (
1000
1005
  currentContextDependency !== null &&
1001
1006
  !Object.hasOwn(currentContextDependency, "memoizedValue")
@@ -1030,6 +1035,7 @@ export const getFiberHooks = (fiber: Fiber): HooksTree => {
1030
1035
  currentContextDependency = null;
1031
1036
  currentThenableState = null;
1032
1037
  currentThenableIndex = 0;
1038
+ currentMemoCacheIndex = 0;
1033
1039
  restoreContexts(contextMap);
1034
1040
  restoreConsole(originalConsoleMethods);
1035
1041
  }