@@ -47,6 +47,7 @@ type HookLogEntry = {
4747 stackError : Error ,
4848 value : mixed ,
4949 debugInfo : ReactDebugInfo | null ,
50+ dispatcherHookName : string ,
5051} ;
5152
5253let hookLog : Array < HookLogEntry > = [ ] ;
@@ -131,6 +132,8 @@ function getPrimitiveStackCache(): Map<string, Array<any>> {
131132 ) ;
132133 } catch ( x ) { }
133134 }
135+
136+ Dispatcher . useId ( ) ;
134137 } finally {
135138 readHookLog = hookLog ;
136139 hookLog = [ ] ;
@@ -207,6 +210,7 @@ function use<T>(usable: Usable<T>): T {
207210 value : fulfilledValue ,
208211 debugInfo :
209212 thenable . _debugInfo === undefined ? null : thenable . _debugInfo ,
213+ dispatcherHookName : 'Use' ,
210214 } ) ;
211215 return fulfilledValue ;
212216 }
@@ -224,6 +228,7 @@ function use<T>(usable: Usable<T>): T {
224228 value : thenable ,
225229 debugInfo :
226230 thenable . _debugInfo === undefined ? null : thenable . _debugInfo ,
231+ dispatcherHookName : 'Use' ,
227232 } );
228233 throw SuspenseException;
229234 } else if ( usable . $$typeof === REACT_CONTEXT_TYPE ) {
@@ -236,6 +241,7 @@ function use<T>(usable: Usable<T>): T {
236241 stackError : new Error ( ) ,
237242 value,
238243 debugInfo : null ,
244+ dispatcherHookName : 'Use' ,
239245 } ) ;
240246
241247 return value ;
@@ -254,6 +260,7 @@ function useContext<T>(context: ReactContext<T>): T {
254260 stackError : new Error ( ) ,
255261 value : value ,
256262 debugInfo : null ,
263+ dispatcherHookName : 'Context' ,
257264 } ) ;
258265 return value ;
259266}
@@ -275,6 +282,7 @@ function useState<S>(
275282 stackError : new Error ( ) ,
276283 value : state ,
277284 debugInfo : null ,
285+ dispatcherHookName : 'State' ,
278286 } ) ;
279287 return [ state , ( action : BasicStateAction < S > ) => { } ] ;
280288}
@@ -297,6 +305,7 @@ function useReducer<S, I, A>(
297305 stackError : new Error ( ) ,
298306 value : state ,
299307 debugInfo : null ,
308+ dispatcherHookName : 'Reducer' ,
300309 } ) ;
301310 return [ state , ( action : A ) => { } ] ;
302311}
@@ -310,6 +319,7 @@ function useRef<T>(initialValue: T): {current: T} {
310319 stackError : new Error ( ) ,
311320 value : ref . current ,
312321 debugInfo : null ,
322+ dispatcherHookName : 'Ref' ,
313323 } ) ;
314324 return ref ;
315325}
@@ -322,6 +332,7 @@ function useCacheRefresh(): () => void {
322332 stackError : new Error ( ) ,
323333 value : hook !== null ? hook . memoizedState : function refresh ( ) { } ,
324334 debugInfo : null ,
335+ dispatcherHookName : 'CacheRefresh' ,
325336 } ) ;
326337 return ( ) = > { } ;
327338}
@@ -337,6 +348,7 @@ function useLayoutEffect(
337348 stackError : new Error ( ) ,
338349 value : create ,
339350 debugInfo : null ,
351+ dispatcherHookName : 'LayoutEffect' ,
340352 } ) ;
341353}
342354
@@ -351,6 +363,7 @@ function useInsertionEffect(
351363 stackError : new Error ( ) ,
352364 value : create ,
353365 debugInfo : null ,
366+ dispatcherHookName : 'InsertionEffect' ,
354367 } ) ;
355368}
356369
@@ -365,6 +378,7 @@ function useEffect(
365378 stackError : new Error ( ) ,
366379 value : create ,
367380 debugInfo : null ,
381+ dispatcherHookName : 'Effect' ,
368382 } ) ;
369383}
370384
@@ -388,6 +402,7 @@ function useImperativeHandle<T>(
388402 stackError : new Error ( ) ,
389403 value : instance ,
390404 debugInfo : null ,
405+ dispatcherHookName : 'ImperativeHandle' ,
391406 } );
392407}
393408
@@ -398,6 +413,7 @@ function useDebugValue(value: any, formatterFn: ?(value: any) => any) {
398413 stackError : new Error ( ) ,
399414 value : typeof formatterFn === 'function' ? formatterFn ( value ) : value ,
400415 debugInfo : null ,
416+ dispatcherHookName : 'DebugValue' ,
401417 } ) ;
402418}
403419
@@ -409,6 +425,7 @@ function useCallback<T>(callback: T, inputs: Array<mixed> | void | null): T {
409425 stackError : new Error ( ) ,
410426 value : hook !== null ? hook . memoizedState [ 0 ] : callback ,
411427 debugInfo : null ,
428+ dispatcherHookName : 'Callback' ,
412429 } ) ;
413430 return callback ;
414431}
@@ -425,6 +442,7 @@ function useMemo<T>(
425442 stackError : new Error ( ) ,
426443 value,
427444 debugInfo : null ,
445+ dispatcherHookName : 'Memo' ,
428446 } ) ;
429447 return value ;
430448}
@@ -446,6 +464,7 @@ function useSyncExternalStore<T>(
446464 stackError : new Error ( ) ,
447465 value,
448466 debugInfo : null ,
467+ dispatcherHookName : 'SyncExternalStore' ,
449468 } ) ;
450469 return value ;
451470}
@@ -468,6 +487,7 @@ function useTransition(): [
468487 stackError : new Error ( ) ,
469488 value : isPending ,
470489 debugInfo : null ,
490+ dispatcherHookName : 'Transition' ,
471491 } ) ;
472492 return [ isPending , ( ) => { } ] ;
473493}
@@ -481,6 +501,7 @@ function useDeferredValue<T>(value: T, initialValue?: T): T {
481501 stackError : new Error ( ) ,
482502 value : prevValue ,
483503 debugInfo : null ,
504+ dispatcherHookName : 'DeferredValue' ,
484505 } ) ;
485506 return prevValue ;
486507}
@@ -494,6 +515,7 @@ function useId(): string {
494515 stackError : new Error ( ) ,
495516 value : id ,
496517 debugInfo : null ,
518+ dispatcherHookName : 'Id' ,
497519 } ) ;
498520 return id ;
499521}
@@ -544,6 +566,7 @@ function useOptimistic<S, A>(
544566 stackError : new Error ( ) ,
545567 value : state ,
546568 debugInfo : null ,
569+ dispatcherHookName : 'Optimistic' ,
547570 } ) ;
548571 return [ state , ( action : A ) => { } ] ;
549572}
@@ -603,6 +626,7 @@ function useFormState<S, P>(
603626 stackError : stackError ,
604627 value : value ,
605628 debugInfo : debugInfo ,
629+ dispatcherHookName : 'FormState' ,
606630 } );
607631
608632 if (error !== null) {
@@ -672,6 +696,7 @@ function useActionState<S, P>(
672696 stackError : stackError ,
673697 value : value ,
674698 debugInfo : debugInfo ,
699+ dispatcherHookName : 'ActionState' ,
675700 } );
676701
677702 if (error !== null) {
@@ -759,8 +784,7 @@ export type HooksTree = Array<HooksNode>;
759784// of a hook call. A simple way to demonstrate this is wrapping `new Error()`
760785// in a wrapper constructor like a polyfill. That'll add an extra frame.
761786// Similar things can happen with the call to the dispatcher. The top frame
762- // may not be the primitive. Likewise the primitive can have fewer stack frames
763- // such as when a call to useState got inlined to use dispatcher.useState.
787+ // may not be the primitive.
764788//
765789// We also can't assume that the last frame of the root call is the same
766790// frame as the last frame of the hook call because long stack traces can be
@@ -810,27 +834,8 @@ function findCommonAncestorIndex(rootStack: any, hookStack: any) {
810834 return - 1 ;
811835}
812836
813- function isReactWrapper ( functionName : any , primitiveName : string ) {
814- if ( ! functionName ) {
815- return false ;
816- }
817- switch (primitiveName) {
818- case 'Context' :
819- case 'Context (use)' :
820- case 'Promise' :
821- case 'Unresolved' :
822- if ( functionName . endsWith ( 'use' ) ) {
823- return true ;
824- }
825- }
826- const expectedPrimitiveName = 'use' + primitiveName;
827- if (functionName.length < expectedPrimitiveName . length ) {
828- return false ;
829- }
830- return (
831- functionName . lastIndexOf ( expectedPrimitiveName ) ===
832- functionName . length - expectedPrimitiveName . length
833- ) ;
837+ function isReactWrapper ( functionName : any , wrapperName : string ) {
838+ return parseHookName ( functionName ) === wrapperName ;
834839}
835840
836841function findPrimitiveIndex(hookStack: any, hook: HookLogEntry) {
@@ -841,17 +846,18 @@ function findPrimitiveIndex(hookStack: any, hook: HookLogEntry) {
841846 }
842847 for (let i = 0; i < primitiveStack . length && i < hookStack . length ; i ++ ) {
843848 if ( primitiveStack [ i ] . source !== hookStack [ i ] . source ) {
844- // If the next two frames are functions called `useX` then we assume that they're part of the
845- // wrappers that the React packager or other packages adds around the dispatcher.
849+ // If the next frame is a method from the dispatcher, we
850+ // assume that the next frame after that is the actual public API call.
851+ // This prohibits nesting dispatcher calls in hooks.
846852 if (
847853 i < hookStack . length - 1 &&
848- isReactWrapper ( hookStack [ i ] . functionName , hook . primitive )
854+ isReactWrapper ( hookStack [ i ] . functionName , hook . dispatcherHookName )
849855 ) {
850856 i ++ ;
851857 }
852858 if (
853859 i < hookStack . length - 1 &&
854- isReactWrapper ( hookStack [ i ] . functionName , hook . primitive )
860+ isReactWrapper ( hookStack [ i ] . functionName , hook . dispatcherHookName )
855861 ) {
856862 i ++ ;
857863 }
@@ -872,21 +878,41 @@ function parseTrimmedStack(rootStack: any, hook: HookLogEntry) {
872878 primitiveIndex === - 1 ||
873879 rootIndex - primitiveIndex < 2
874880 ) {
875- // Something went wrong. Give up.
876- return null ;
881+ if ( primitiveIndex === - 1 ) {
882+ // Something went wrong. Give up.
883+ return [ null , null ] ;
884+ } else {
885+ return [ hookStack [ primitiveIndex - 1 ] , null ] ;
886+ }
877887 }
878- return hookStack . slice ( primitiveIndex , rootIndex - 1 ) ;
888+ return [
889+ hookStack [ primitiveIndex - 1 ] ,
890+ hookStack . slice ( primitiveIndex , rootIndex - 1 ) ,
891+ ] ;
879892}
880893
881- function parseCustomHookName ( functionName : void | string ) : string {
894+ function parseHookName ( functionName : void | string ) : string {
882895 if ( ! functionName ) {
883896 return '';
884897 }
885- let startIndex = functionName . lastIndexOf ( '. ');
898+ let startIndex = functionName . lastIndexOf ( '[ as ') ;
899+
900+ if ( startIndex !== - 1 ) {
901+ // Workaround for sourcemaps in Jest and Chrome.
902+ // In `node --enable-source-maps`, we don't see "Object.useHostTransitionStatus [as useFormStatus]" but "Object.useFormStatus"
903+ // "Object.useHostTransitionStatus [as useFormStatus]" -> "useFormStatus"
904+ return parseHookName ( functionName . slice ( startIndex + '[as ' . length , - 1 ) ) ;
905+ }
906+ startIndex = functionName . lastIndexOf ( '. ') ;
886907 if ( startIndex = = = - 1 ) {
887908 startIndex = 0 ;
909+ } else {
910+ startIndex += 1 ;
888911 }
889912 if ( functionName . slice ( startIndex , startIndex + 3 ) === 'use ') {
913+ if ( functionName . length - startIndex === 3 ) {
914+ return 'Use' ;
915+ }
890916 startIndex += 3 ;
891917 }
892918 return functionName . slice ( startIndex ) ;
@@ -903,7 +929,17 @@ function buildTree(
903929 const stackOfChildren = [ ] ;
904930 for ( let i = 0 ; i < readHookLog . length ; i ++ ) {
905931 const hook = readHookLog [ i ] ;
906- const stack = parseTrimmedStack ( rootStack , hook ) ;
932+ const parseResult = parseTrimmedStack ( rootStack , hook ) ;
933+ const primitiveFrame = parseResult [ 0 ] ;
934+ const stack = parseResult [ 1 ] ;
935+ let displayName = hook . displayName ;
936+ if ( displayName === null && primitiveFrame !== null ) {
937+ displayName =
938+ parseHookName ( primitiveFrame . functionName ) ||
939+ // Older versions of React do not have sourcemaps.
940+ // In those versions there was always a 1:1 mapping between wrapper and dispatcher method.
941+ parseHookName ( hook . dispatcherHookName ) ;
942+ }
907943 if ( stack !== null ) {
908944 // Note: The indices 0 <= n < length-1 will contain the names.
909945 // The indices 1 <= n < length will contain the source locations.
@@ -934,7 +970,7 @@ function buildTree(
934970 const levelChild : HooksNode = {
935971 id : null ,
936972 isStateEditable : false ,
937- name : parseCustomHookName ( stack [ j - 1 ] . functionName ) ,
973+ name : parseHookName ( stack [ j - 1 ] . functionName ) ,
938974 value : undefined ,
939975 subHooks : children ,
940976 debugInfo : null ,
@@ -952,7 +988,7 @@ function buildTree(
952988 }
953989 prevStack = stack ;
954990 }
955- const { displayName , primitive , debugInfo } = hook;
991+ const { primitive , debugInfo } = hook;
956992
957993 // For now, the "id" of stateful hooks is just the stateful hook index.
958994 // Custom hooks have no ids, nor do non-stateful native hooks (e.g. Context, DebugValue).
0 commit comments