@@ -47,6 +47,7 @@ type HookLogEntry = {
47
47
stackError : Error ,
48
48
value : mixed ,
49
49
debugInfo : ReactDebugInfo | null ,
50
+ dispatcherHookName : string ,
50
51
} ;
51
52
52
53
let hookLog : Array < HookLogEntry > = [ ] ;
@@ -131,6 +132,8 @@ function getPrimitiveStackCache(): Map<string, Array<any>> {
131
132
) ;
132
133
} catch ( x ) { }
133
134
}
135
+
136
+ Dispatcher . useId ( ) ;
134
137
} finally {
135
138
readHookLog = hookLog ;
136
139
hookLog = [ ] ;
@@ -207,6 +210,7 @@ function use<T>(usable: Usable<T>): T {
207
210
value : fulfilledValue ,
208
211
debugInfo :
209
212
thenable . _debugInfo === undefined ? null : thenable . _debugInfo ,
213
+ dispatcherHookName : 'Use' ,
210
214
} ) ;
211
215
return fulfilledValue ;
212
216
}
@@ -224,6 +228,7 @@ function use<T>(usable: Usable<T>): T {
224
228
value : thenable ,
225
229
debugInfo :
226
230
thenable . _debugInfo === undefined ? null : thenable . _debugInfo ,
231
+ dispatcherHookName : 'Use' ,
227
232
} );
228
233
throw SuspenseException;
229
234
} else if ( usable . $$typeof === REACT_CONTEXT_TYPE ) {
@@ -236,6 +241,7 @@ function use<T>(usable: Usable<T>): T {
236
241
stackError : new Error ( ) ,
237
242
value,
238
243
debugInfo : null ,
244
+ dispatcherHookName : 'Use' ,
239
245
} ) ;
240
246
241
247
return value ;
@@ -254,6 +260,7 @@ function useContext<T>(context: ReactContext<T>): T {
254
260
stackError : new Error ( ) ,
255
261
value : value ,
256
262
debugInfo : null ,
263
+ dispatcherHookName : 'Context' ,
257
264
} ) ;
258
265
return value ;
259
266
}
@@ -275,6 +282,7 @@ function useState<S>(
275
282
stackError : new Error ( ) ,
276
283
value : state ,
277
284
debugInfo : null ,
285
+ dispatcherHookName : 'State' ,
278
286
} ) ;
279
287
return [ state , ( action : BasicStateAction < S > ) => { } ] ;
280
288
}
@@ -297,6 +305,7 @@ function useReducer<S, I, A>(
297
305
stackError : new Error ( ) ,
298
306
value : state ,
299
307
debugInfo : null ,
308
+ dispatcherHookName : 'Reducer' ,
300
309
} ) ;
301
310
return [ state , ( action : A ) => { } ] ;
302
311
}
@@ -310,6 +319,7 @@ function useRef<T>(initialValue: T): {current: T} {
310
319
stackError : new Error ( ) ,
311
320
value : ref . current ,
312
321
debugInfo : null ,
322
+ dispatcherHookName : 'Ref' ,
313
323
} ) ;
314
324
return ref ;
315
325
}
@@ -322,6 +332,7 @@ function useCacheRefresh(): () => void {
322
332
stackError : new Error ( ) ,
323
333
value : hook !== null ? hook . memoizedState : function refresh ( ) { } ,
324
334
debugInfo : null ,
335
+ dispatcherHookName : 'CacheRefresh' ,
325
336
} ) ;
326
337
return ( ) = > { } ;
327
338
}
@@ -337,6 +348,7 @@ function useLayoutEffect(
337
348
stackError : new Error ( ) ,
338
349
value : create ,
339
350
debugInfo : null ,
351
+ dispatcherHookName : 'LayoutEffect' ,
340
352
} ) ;
341
353
}
342
354
@@ -351,6 +363,7 @@ function useInsertionEffect(
351
363
stackError : new Error ( ) ,
352
364
value : create ,
353
365
debugInfo : null ,
366
+ dispatcherHookName : 'InsertionEffect' ,
354
367
} ) ;
355
368
}
356
369
@@ -365,6 +378,7 @@ function useEffect(
365
378
stackError : new Error ( ) ,
366
379
value : create ,
367
380
debugInfo : null ,
381
+ dispatcherHookName : 'Effect' ,
368
382
} ) ;
369
383
}
370
384
@@ -388,6 +402,7 @@ function useImperativeHandle<T>(
388
402
stackError : new Error ( ) ,
389
403
value : instance ,
390
404
debugInfo : null ,
405
+ dispatcherHookName : 'ImperativeHandle' ,
391
406
} );
392
407
}
393
408
@@ -398,6 +413,7 @@ function useDebugValue(value: any, formatterFn: ?(value: any) => any) {
398
413
stackError : new Error ( ) ,
399
414
value : typeof formatterFn === 'function' ? formatterFn ( value ) : value ,
400
415
debugInfo : null ,
416
+ dispatcherHookName : 'DebugValue' ,
401
417
} ) ;
402
418
}
403
419
@@ -409,6 +425,7 @@ function useCallback<T>(callback: T, inputs: Array<mixed> | void | null): T {
409
425
stackError : new Error ( ) ,
410
426
value : hook !== null ? hook . memoizedState [ 0 ] : callback ,
411
427
debugInfo : null ,
428
+ dispatcherHookName : 'Callback' ,
412
429
} ) ;
413
430
return callback ;
414
431
}
@@ -425,6 +442,7 @@ function useMemo<T>(
425
442
stackError : new Error ( ) ,
426
443
value,
427
444
debugInfo : null ,
445
+ dispatcherHookName : 'Memo' ,
428
446
} ) ;
429
447
return value ;
430
448
}
@@ -446,6 +464,7 @@ function useSyncExternalStore<T>(
446
464
stackError : new Error ( ) ,
447
465
value,
448
466
debugInfo : null ,
467
+ dispatcherHookName : 'SyncExternalStore' ,
449
468
} ) ;
450
469
return value ;
451
470
}
@@ -468,6 +487,7 @@ function useTransition(): [
468
487
stackError : new Error ( ) ,
469
488
value : isPending ,
470
489
debugInfo : null ,
490
+ dispatcherHookName : 'Transition' ,
471
491
} ) ;
472
492
return [ isPending , ( ) => { } ] ;
473
493
}
@@ -481,6 +501,7 @@ function useDeferredValue<T>(value: T, initialValue?: T): T {
481
501
stackError : new Error ( ) ,
482
502
value : prevValue ,
483
503
debugInfo : null ,
504
+ dispatcherHookName : 'DeferredValue' ,
484
505
} ) ;
485
506
return prevValue ;
486
507
}
@@ -494,6 +515,7 @@ function useId(): string {
494
515
stackError : new Error ( ) ,
495
516
value : id ,
496
517
debugInfo : null ,
518
+ dispatcherHookName : 'Id' ,
497
519
} ) ;
498
520
return id ;
499
521
}
@@ -544,6 +566,7 @@ function useOptimistic<S, A>(
544
566
stackError : new Error ( ) ,
545
567
value : state ,
546
568
debugInfo : null ,
569
+ dispatcherHookName : 'Optimistic' ,
547
570
} ) ;
548
571
return [ state , ( action : A ) => { } ] ;
549
572
}
@@ -603,6 +626,7 @@ function useFormState<S, P>(
603
626
stackError : stackError ,
604
627
value : value ,
605
628
debugInfo : debugInfo ,
629
+ dispatcherHookName : 'FormState' ,
606
630
} );
607
631
608
632
if (error !== null) {
@@ -672,6 +696,7 @@ function useActionState<S, P>(
672
696
stackError : stackError ,
673
697
value : value ,
674
698
debugInfo : debugInfo ,
699
+ dispatcherHookName : 'ActionState' ,
675
700
} );
676
701
677
702
if (error !== null) {
@@ -759,8 +784,7 @@ export type HooksTree = Array<HooksNode>;
759
784
// of a hook call. A simple way to demonstrate this is wrapping `new Error()`
760
785
// in a wrapper constructor like a polyfill. That'll add an extra frame.
761
786
// 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.
764
788
//
765
789
// We also can't assume that the last frame of the root call is the same
766
790
// 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) {
810
834
return - 1 ;
811
835
}
812
836
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 ;
834
839
}
835
840
836
841
function findPrimitiveIndex(hookStack: any, hook: HookLogEntry) {
@@ -841,17 +846,18 @@ function findPrimitiveIndex(hookStack: any, hook: HookLogEntry) {
841
846
}
842
847
for (let i = 0; i < primitiveStack . length && i < hookStack . length ; i ++ ) {
843
848
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.
846
852
if (
847
853
i < hookStack . length - 1 &&
848
- isReactWrapper ( hookStack [ i ] . functionName , hook . primitive )
854
+ isReactWrapper ( hookStack [ i ] . functionName , hook . dispatcherHookName )
849
855
) {
850
856
i ++ ;
851
857
}
852
858
if (
853
859
i < hookStack . length - 1 &&
854
- isReactWrapper ( hookStack [ i ] . functionName , hook . primitive )
860
+ isReactWrapper ( hookStack [ i ] . functionName , hook . dispatcherHookName )
855
861
) {
856
862
i ++ ;
857
863
}
@@ -872,21 +878,41 @@ function parseTrimmedStack(rootStack: any, hook: HookLogEntry) {
872
878
primitiveIndex === - 1 ||
873
879
rootIndex - primitiveIndex < 2
874
880
) {
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
+ }
877
887
}
878
- return hookStack . slice ( primitiveIndex , rootIndex - 1 ) ;
888
+ return [
889
+ hookStack [ primitiveIndex - 1 ] ,
890
+ hookStack . slice ( primitiveIndex , rootIndex - 1 ) ,
891
+ ] ;
879
892
}
880
893
881
- function parseCustomHookName ( functionName : void | string ) : string {
894
+ function parseHookName ( functionName : void | string ) : string {
882
895
if ( ! functionName ) {
883
896
return '';
884
897
}
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 ( '. ') ;
886
907
if ( startIndex = = = - 1 ) {
887
908
startIndex = 0 ;
909
+ } else {
910
+ startIndex += 1 ;
888
911
}
889
912
if ( functionName . slice ( startIndex , startIndex + 3 ) === 'use ') {
913
+ if ( functionName . length - startIndex === 3 ) {
914
+ return 'Use' ;
915
+ }
890
916
startIndex += 3 ;
891
917
}
892
918
return functionName . slice ( startIndex ) ;
@@ -903,7 +929,17 @@ function buildTree(
903
929
const stackOfChildren = [ ] ;
904
930
for ( let i = 0 ; i < readHookLog . length ; i ++ ) {
905
931
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
+ }
907
943
if ( stack !== null ) {
908
944
// Note: The indices 0 <= n < length-1 will contain the names.
909
945
// The indices 1 <= n < length will contain the source locations.
@@ -934,7 +970,7 @@ function buildTree(
934
970
const levelChild : HooksNode = {
935
971
id : null ,
936
972
isStateEditable : false ,
937
- name : parseCustomHookName ( stack [ j - 1 ] . functionName ) ,
973
+ name : parseHookName ( stack [ j - 1 ] . functionName ) ,
938
974
value : undefined ,
939
975
subHooks : children ,
940
976
debugInfo : null ,
@@ -952,7 +988,7 @@ function buildTree(
952
988
}
953
989
prevStack = stack ;
954
990
}
955
- const { displayName , primitive , debugInfo } = hook;
991
+ const { primitive , debugInfo } = hook;
956
992
957
993
// For now, the "id" of stateful hooks is just the stateful hook index.
958
994
// Custom hooks have no ids, nor do non-stateful native hooks (e.g. Context, DebugValue).
0 commit comments