@@ -45,6 +45,7 @@ type HookLogEntry = {
4545 stackError : Error ,
4646 value : mixed ,
4747 debugInfo : ReactDebugInfo | null ,
48+ dispatcherMethodName : string ,
4849} ;
4950
5051let hookLog : Array < HookLogEntry > = [ ] ;
@@ -130,6 +131,7 @@ function getPrimitiveStackCache(): Map<string, Array<any>> {
130131 // This type check is for Flow only.
131132 Dispatcher . useHostTransitionStatus ( ) ;
132133 }
134+ Dispatcher . useId ( ) ;
133135 } finally {
134136 readHookLog = hookLog ;
135137 hookLog = [ ] ;
@@ -200,6 +202,7 @@ function use<T>(usable: Usable<T>): T {
200202 value : fulfilledValue ,
201203 debugInfo :
202204 thenable . _debugInfo === undefined ? null : thenable . _debugInfo ,
205+ dispatcherMethodName : 'use' ,
203206 } ) ;
204207 return fulfilledValue ;
205208 }
@@ -217,6 +220,7 @@ function use<T>(usable: Usable<T>): T {
217220 value : thenable ,
218221 debugInfo :
219222 thenable . _debugInfo === undefined ? null : thenable . _debugInfo ,
223+ dispatcherMethodName : 'use' ,
220224 } );
221225 throw SuspenseException;
222226 } else if ( usable . $$typeof === REACT_CONTEXT_TYPE ) {
@@ -229,6 +233,7 @@ function use<T>(usable: Usable<T>): T {
229233 stackError : new Error ( ) ,
230234 value,
231235 debugInfo : null ,
236+ dispatcherMethodName : 'use' ,
232237 } ) ;
233238
234239 return value ;
@@ -247,6 +252,7 @@ function useContext<T>(context: ReactContext<T>): T {
247252 stackError : new Error ( ) ,
248253 value : value ,
249254 debugInfo : null ,
255+ dispatcherMethodName : 'useContext' ,
250256 } ) ;
251257 return value ;
252258}
@@ -268,6 +274,7 @@ function useState<S>(
268274 stackError : new Error ( ) ,
269275 value : state ,
270276 debugInfo : null ,
277+ dispatcherMethodName : 'useState' ,
271278 } ) ;
272279 return [ state , ( action : BasicStateAction < S > ) => { } ] ;
273280}
@@ -290,6 +297,7 @@ function useReducer<S, I, A>(
290297 stackError : new Error ( ) ,
291298 value : state ,
292299 debugInfo : null ,
300+ dispatcherMethodName : 'useReducer' ,
293301 } ) ;
294302 return [ state , ( action : A ) => { } ] ;
295303}
@@ -303,6 +311,7 @@ function useRef<T>(initialValue: T): {current: T} {
303311 stackError : new Error ( ) ,
304312 value : ref . current ,
305313 debugInfo : null ,
314+ dispatcherMethodName : 'useRef' ,
306315 } ) ;
307316 return ref ;
308317}
@@ -315,6 +324,7 @@ function useCacheRefresh(): () => void {
315324 stackError : new Error ( ) ,
316325 value : hook !== null ? hook . memoizedState : function refresh ( ) { } ,
317326 debugInfo : null ,
327+ dispatcherMethodName : 'useCacheRefresh' ,
318328 } ) ;
319329 return ( ) = > { } ;
320330}
@@ -330,6 +340,7 @@ function useLayoutEffect(
330340 stackError : new Error ( ) ,
331341 value : create ,
332342 debugInfo : null ,
343+ dispatcherMethodName : 'useLayoutEffect' ,
333344 } ) ;
334345}
335346
@@ -344,6 +355,7 @@ function useInsertionEffect(
344355 stackError : new Error ( ) ,
345356 value : create ,
346357 debugInfo : null ,
358+ dispatcherMethodName : 'useInsertionEffect' ,
347359 } ) ;
348360}
349361
@@ -358,6 +370,7 @@ function useEffect(
358370 stackError : new Error ( ) ,
359371 value : create ,
360372 debugInfo : null ,
373+ dispatcherMethodName : 'useEffect' ,
361374 } ) ;
362375}
363376
@@ -381,6 +394,7 @@ function useImperativeHandle<T>(
381394 stackError : new Error ( ) ,
382395 value : instance ,
383396 debugInfo : null ,
397+ dispatcherMethodName : 'useImperativeHandle' ,
384398 } );
385399}
386400
@@ -391,6 +405,7 @@ function useDebugValue(value: any, formatterFn: ?(value: any) => any) {
391405 stackError : new Error ( ) ,
392406 value : typeof formatterFn === 'function' ? formatterFn ( value ) : value ,
393407 debugInfo : null ,
408+ dispatcherMethodName : 'useDebugValue' ,
394409 } ) ;
395410}
396411
@@ -402,6 +417,7 @@ function useCallback<T>(callback: T, inputs: Array<mixed> | void | null): T {
402417 stackError : new Error ( ) ,
403418 value : hook !== null ? hook . memoizedState [ 0 ] : callback ,
404419 debugInfo : null ,
420+ dispatcherMethodName : 'useCallback' ,
405421 } ) ;
406422 return callback ;
407423}
@@ -418,6 +434,7 @@ function useMemo<T>(
418434 stackError : new Error ( ) ,
419435 value,
420436 debugInfo : null ,
437+ dispatcherMethodName : 'useMemo' ,
421438 } ) ;
422439 return value ;
423440}
@@ -439,6 +456,7 @@ function useSyncExternalStore<T>(
439456 stackError : new Error ( ) ,
440457 value,
441458 debugInfo : null ,
459+ dispatcherMethodName : 'useSyncExternalStore' ,
442460 } ) ;
443461 return value ;
444462}
@@ -458,6 +476,7 @@ function useTransition(): [
458476 stackError : new Error ( ) ,
459477 value : undefined ,
460478 debugInfo : null ,
479+ dispatcherMethodName : 'useTransition' ,
461480 } ) ;
462481 return [ false , callback => { } ] ;
463482}
@@ -470,6 +489,7 @@ function useDeferredValue<T>(value: T, initialValue?: T): T {
470489 stackError : new Error ( ) ,
471490 value : hook !== null ? hook . memoizedState : value ,
472491 debugInfo : null ,
492+ dispatcherMethodName : 'useDeferredValue' ,
473493 } ) ;
474494 return value ;
475495}
@@ -483,6 +503,7 @@ function useId(): string {
483503 stackError : new Error ( ) ,
484504 value : id ,
485505 debugInfo : null ,
506+ dispatcherMethodName : 'useId' ,
486507 } ) ;
487508 return id ;
488509}
@@ -533,6 +554,7 @@ function useOptimistic<S, A>(
533554 stackError : new Error ( ) ,
534555 value : state ,
535556 debugInfo : null ,
557+ dispatcherMethodName : 'useOptimistic' ,
536558 } ) ;
537559 return [ state , ( action : A ) => { } ] ;
538560}
@@ -591,6 +613,7 @@ function useFormState<S, P>(
591613 stackError : stackError ,
592614 value : value ,
593615 debugInfo : debugInfo ,
616+ dispatcherMethodName : 'useFormState' ,
594617 } );
595618
596619 if (error !== null) {
@@ -618,6 +641,7 @@ function useHostTransitionStatus(): TransitionStatus {
618641 stackError : new Error ( ) ,
619642 value : status ,
620643 debugInfo : null ,
644+ dispatcherMethodName : 'HostTransitionStatus' ,
621645 } ) ;
622646
623647 return status ;
@@ -696,8 +720,7 @@ export type HooksTree = Array<HooksNode>;
696720// of a hook call. A simple way to demonstrate this is wrapping `new Error()`
697721// in a wrapper constructor like a polyfill. That'll add an extra frame.
698722// Similar things can happen with the call to the dispatcher. The top frame
699- // may not be the primitive. Likewise the primitive can have fewer stack frames
700- // such as when a call to useState got inlined to use dispatcher.useState.
723+ // may not be the primitive.
701724//
702725// We also can't assume that the last frame of the root call is the same
703726// frame as the last frame of the hook call because long stack traces can be
@@ -747,26 +770,16 @@ function findCommonAncestorIndex(rootStack: any, hookStack: any) {
747770 return - 1 ;
748771}
749772
750- function isReactWrapper ( functionName : any , primitiveName : string ) {
773+ function isReactWrapper ( functionName : any , wrapperName : string ) {
751774 if ( ! functionName ) {
752775 return false ;
753776 }
754- switch (primitiveName) {
755- case 'Context' :
756- case 'Context (use)' :
757- case 'Promise' :
758- case 'Unresolved' :
759- if ( functionName . endsWith ( 'use' ) ) {
760- return true ;
761- }
762- }
763- const expectedPrimitiveName = 'use' + primitiveName;
764- if (functionName.length < expectedPrimitiveName . length ) {
777+ if (functionName.length < wrapperName . length ) {
765778 return false ;
766779 }
767780 return (
768- functionName . lastIndexOf ( expectedPrimitiveName ) ===
769- functionName . length - expectedPrimitiveName . length
781+ functionName . lastIndexOf ( wrapperName ) ===
782+ functionName . length - wrapperName . length
770783 ) ;
771784}
772785
@@ -778,18 +791,14 @@ function findPrimitiveIndex(hookStack: any, hook: HookLogEntry) {
778791 }
779792 for ( let i = 0 ; i < primitiveStack . length && i < hookStack . length ; i ++ ) {
780793 if ( primitiveStack [ i ] . source !== hookStack [ i ] . source ) {
781- // If the next two frames are functions called `useX` then we assume that they're part of the
782- // wrappers that the React packager or other packages adds around the dispatcher.
794+ // If the next frame is a method from the dispatcher, we
795+ // assume that the next frame after that is the actual public API call.
796+ // This prohibits nesting dispatcher calls in hooks.
783797 if (
784798 i < hookStack . length - 1 &&
785- isReactWrapper ( hookStack [ i ] . functionName , hook . primitive )
799+ isReactWrapper ( hookStack [ i ] . functionName , hook . dispatcherMethodName )
786800 ) {
787801 i ++ ;
788- }
789- if (
790- i < hookStack . length - 1 &&
791- isReactWrapper ( hookStack [ i ] . functionName , hook . primitive )
792- ) {
793802 i ++ ;
794803 }
795804 return i ;
@@ -812,16 +821,21 @@ function parseTrimmedStack(rootStack: any, hook: HookLogEntry) {
812821 // Something went wrong. Give up.
813822 return null ;
814823 }
815- return hookStack . slice ( primitiveIndex , rootIndex - 1 ) ;
824+ return [
825+ hookStack [ primitiveIndex - 1 ] ,
826+ hookStack . slice ( primitiveIndex , rootIndex - 1 ) ,
827+ ] ;
816828}
817829
818- function parseCustomHookName ( functionName : void | string ) : string {
830+ function parseHookName ( functionName : void | string ) : string {
819831 if ( ! functionName ) {
820832 return '' ;
821833 }
822834 let startIndex = functionName . lastIndexOf ( '.' ) ;
823835 if ( startIndex === - 1 ) {
824836 startIndex = 0 ;
837+ } else {
838+ startIndex + = 1 ;
825839 }
826840 if ( functionName . slice ( startIndex , startIndex + 3 ) === 'use' ) {
827841 startIndex += 3 ;
@@ -840,8 +854,14 @@ function buildTree(
840854 const stackOfChildren = [];
841855 for (let i = 0; i < readHookLog . length ; i ++ ) {
842856 const hook = readHookLog [ i ] ;
843- const stack = parseTrimmedStack ( rootStack , hook ) ;
844- if ( stack !== null ) {
857+ const parseResult = parseTrimmedStack ( rootStack , hook ) ;
858+ let displayName = hook . displayName ;
859+ if ( parseResult !== null ) {
860+ const [ primitiveFrame , stack ] = parseResult ;
861+ if ( hook . displayName === null ) {
862+ // TODO: Support older versions of React without sourcemaps by using the primitive name if primitiveFrame.functionName does not look like a hook.
863+ displayName = parseHookName ( primitiveFrame . functionName ) ;
864+ }
845865 // Note: The indices 0 <= n < length-1 will contain the names.
846866 // The indices 1 <= n < length will contain the source locations.
847867 // That's why we get the name from n - 1 and don't check the source
@@ -871,7 +891,7 @@ function buildTree(
871891 const levelChild: HooksNode = {
872892 id : null ,
873893 isStateEditable : false ,
874- name : parseCustomHookName ( stack [ j - 1 ] . functionName ) ,
894+ name : parseHookName ( stack [ j - 1 ] . functionName ) ,
875895 value : undefined ,
876896 subHooks : children ,
877897 debugInfo : null ,
@@ -889,7 +909,7 @@ function buildTree(
889909 }
890910 prevStack = stack ;
891911 }
892- const { displayName , primitive , debugInfo } = hook;
912+ const { primitive , debugInfo } = hook;
893913
894914 // For now, the "id" of stateful hooks is just the stateful hook index.
895915 // Custom hooks have no ids, nor do non-stateful native hooks (e.g. Context, DebugValue).
@@ -905,11 +925,11 @@ function buildTree(
905925
906926 // For the time being, only State and Reducer hooks support runtime overrides.
907927 const isStateEditable = primitive === 'Reducer' || primitive === 'State';
908- const name = displayName || primitive;
928+
909929 const levelChild: HooksNode = {
910930 id ,
911931 isStateEditable ,
912- name : name ,
932+ name : displayName || 'Unknown' ,
913933 value : hook . value ,
914934 subHooks : [ ] ,
915935 debugInfo : debugInfo ,
@@ -922,6 +942,7 @@ function buildTree(
922942 fileName : null ,
923943 columnNumber : null ,
924944 } ;
945+ const stack = parseResult !== null ? parseResult[1] : null;
925946 if (stack && stack . length >= 1 ) {
926947 const stackFrame = stack [ 0 ] ;
927948 hookSource . lineNumber = stackFrame . lineNumber ;
0 commit comments