54
eagerState: S | null,
55
};
56
57
+type HookType =
58
+ | 'useState'
59
+ | 'useReducer'
60
+ | 'useContext'
61
+ | 'useRef'
62
+ | 'useEffect'
63
+ | 'useLayoutEffect'
64
+ | 'useCallback'
65
+ | 'useMemo'
66
+ | 'useImperativeHandle'
67
+ | 'useDebugValue';
68
+
69
+// the first instance of a hook mismatch in a component,
70
+// represented by a portion of it's stacktrace
71
+let currentHookMismatch = null;
72
+
73
+let didWarnAboutMismatchedHooksForComponent;
74
+if (__DEV__) {
75
+ didWarnAboutMismatchedHooksForComponent = new Set();
76
+}
77
+
78
export type Hook = {
79
memoizedState: any,
80
85
next: Hook | null,
86
};
87
88
+type HookDev = Hook & {
89
+ _debugType: HookType,
90
+};
91
+
92
type Effect = {
93
tag: HookEffectTag,
94
create: () => mixed,
143
const RE_RENDER_LIMIT = 25;
144
145
// In DEV, this is the name of the currently executing primitive hook
121
-let currentHookNameInDev: ?string;
146
+let currentHookNameInDev: ?HookType = null;
147
148
function resolveCurrentlyRenderingFiber(): Fiber {
149
invariant(
195
return true;
196
}
197
198
+// till we have String::padEnd, a small function to
199
+// right-pad strings with spaces till a minimum length
200
+function padEndSpaces(string: string, length: number) {
201
+ if (__DEV__) {
202
+ if (string.length >= length) {
203
+ return string;
204
+ } else {
205
+ return string + ' ' + new Array(length - string.length).join(' ');
206
+ }
207
+ }
208
+}
209
+
210
+function flushHookMismatchWarnings() {
211
+ // we'll show the diff of the low level hooks,
212
+ // and a stack trace so the dev can locate where
213
+ // the first mismatch is coming from
214
+ if (__DEV__) {
215
+ if (currentHookMismatch !== null) {
216
+ let componentName = getComponentName(
217
+ ((currentlyRenderingFiber: any): Fiber).type,
218
+ );
219
+ if (!didWarnAboutMismatchedHooksForComponent.has(componentName)) {
220
+ didWarnAboutMismatchedHooksForComponent.add(componentName);
221
+ const hookStackDiff = [];
222
+ let current = firstCurrentHook;
223
+ const previousOrder = [];
224
+ while (current !== null) {
225
+ previousOrder.push(((current: any): HookDev)._debugType);
226
+ current = current.next;
227
+ }
228
+ let workInProgress = firstWorkInProgressHook;
229
+ const nextOrder = [];
230
+ while (workInProgress !== null) {
231
+ nextOrder.push(((workInProgress: any): HookDev)._debugType);
232
+ workInProgress = workInProgress.next;
233
+ }
234
+ // some bookkeeping for formatting the output table
235
+ const columnLength = Math.max.apply(
236
+ null,
237
+ previousOrder
238
+ .map(hook => hook.length)
239
+ .concat(' Previous render'.length),
240
+ );
241
+
242
+ let hookStackHeader =
243
+ ((padEndSpaces(' Previous render', columnLength): any): string) +
244
+ ' Next render\n';
245
+ const hookStackWidth = hookStackHeader.length;
246
+ hookStackHeader += ' ' + new Array(hookStackWidth - 2).join('-');
247
+ const hookStackFooter = ' ' + new Array(hookStackWidth - 2).join('^');
248
+
249
+ const hookStackLength = Math.max(
250
+ previousOrder.length,
251
+ nextOrder.length,
252
+ );
253
+ for (let i = 0; i < hookStackLength; i++) {
254
+ hookStackDiff.push(
255
+ ((padEndSpaces(i + 1 + '. ', 3): any): string) +
256
+ ((padEndSpaces(previousOrder[i], columnLength): any): string) +
257
+ ' ' +
258
+ nextOrder[i],
259
+ );
260
+ if (previousOrder[i] !== nextOrder[i]) {
261
+ break;
262
+ }
263
+ }
264
+ warning(
265
+ false,
266
+ 'React has detected a change in the order of Hooks called by %s. ' +
267
+ 'This will lead to bugs and errors if not fixed. ' +
268
+ 'For more information, read the Rules of Hooks: https://fb.me/rules-of-hooks\n\n' +
269
+ '%s\n' +
270
+ '%s\n' +
271
+ '%s\n' +
272
+ 'The first Hook type mismatch occured at:\n' +
273
+ '%s\n\n' +
274
+ 'This error occurred in the following component:',
275
+ componentName,
276
+ hookStackHeader,
277
+ hookStackDiff.join('\n'),
278
+ hookStackFooter,
279
+ currentHookMismatch,
280
+ );
281
+ }
282
+ currentHookMismatch = null;
283
+ }
284
+ }
285
+}
286
+
287
export function renderWithHooks(
288
current: Fiber | null,
289
workInProgress: Fiber,
335
getComponentName(Component),
336
);
337
}
338
+ flushHookMismatchWarnings();
339
}
340
} while (didScheduleRenderPhaseUpdate);
341
363
componentUpdateQueue = null;
364
365
if (__DEV__) {
251
- currentHookNameInDev = undefined;
366
+ currentHookNameInDev = null;
367
}
368
369
// These were reset above
396
if (!enableHooks) {
397
return;
398
}
399
+ if (__DEV__) {
400
+ flushHookMismatchWarnings();
401
+ }
402
403
// This is used to reset the state of this module when a component throws.
404
// It's also called inside mountIndeterminateComponent if we determine the
415
componentUpdateQueue = null;
416
417
if (__DEV__) {
300
- currentHookNameInDev = undefined;
418
+ currentHookNameInDev = null;
419
}
420
421
didScheduleRenderPhaseUpdate = false;
424
}
425
426
function createHook(): Hook {
309
- return {
310
- memoizedState: null,
427
+ let hook: Hook = __DEV__
428
+ ? {
429
+ _debugType: ((currentHookNameInDev: any): HookType),
430
+ memoizedState: null,
431
312
- baseState: null,
313
- queue: null,
314
- baseUpdate: null,
432
+ baseState: null,
433
+ queue: null,
434
+ baseUpdate: null,
435
316
- next: null,
317
- };
436
+ next: null,
437
+ }
438
+ : {
439
+ memoizedState: null,
440
+
441
+ baseState: null,
442
+ queue: null,
443
+ baseUpdate: null,
444
+
445
+ next: null,
446
+ };
447
+
448
+ return hook;
449
}
450
451
function cloneHook(hook: Hook): Hook {
321
- return {
322
- memoizedState: hook.memoizedState,
452
+ let nextHook: Hook = __DEV__
453
+ ? {
454
+ _debugType: ((currentHookNameInDev: any): HookType),
455
+ memoizedState: hook.memoizedState,
456
324
- baseState: hook.baseState,
325
- queue: hook.queue,
326
- baseUpdate: hook.baseUpdate,
457
+ baseState: hook.baseState,
458
+ queue: hook.queue,
459
+ baseUpdate: hook.baseUpdate,
460
328
- next: null,
329
- };
461
+ next: null,
462
+ }
463
+ : {
464
+ memoizedState: hook.memoizedState,
465
+
466
+ baseState: hook.baseState,
467
+ queue: hook.queue,
468
+ baseUpdate: hook.baseUpdate,
469
+
470
+ next: null,
471
+ };
472
+
473
+ if (__DEV__) {
474
+ if (!currentHookMismatch) {
475
+ if (currentHookNameInDev !== ((hook: any): HookDev)._debugType) {
476
+ currentHookMismatch = new Error('tracer').stack
477
+ .split('\n')
478
+ .slice(4)
479
+ .join('\n');
480
+ }
481
+ }
482
+ }
483
+ return nextHook;
484
}
485
486
function createWorkInProgressHook(): Hook {
544
): T {
545
if (__DEV__) {
546
currentHookNameInDev = 'useContext';
547
+ createWorkInProgressHook();
548
+ currentHookNameInDev = null;
549
}
550
// Ensure we're in a function component (class components support only the
551
// .unstable_read() form)
578
}
579
let fiber = (currentlyRenderingFiber = resolveCurrentlyRenderingFiber());
580
workInProgressHook = createWorkInProgressHook();
581
+ currentHookNameInDev = null;
582
let queue: UpdateQueue<S, A> | null = (workInProgressHook.queue: any);
583
if (queue !== null) {
584
// Already have a queue, so this is an update.
757
758
export function useRef<T>(initialValue: T): {current: T} {
759
currentlyRenderingFiber = resolveCurrentlyRenderingFiber();
760
+ if (__DEV__) {
761
+ currentHookNameInDev = 'useRef';
762
+ }
763
workInProgressHook = createWorkInProgressHook();
764
+ currentHookNameInDev = null;
765
let ref;
766
767
if (workInProgressHook.memoizedState === null) {
781
deps: Array<mixed> | void | null,
782
): void {
783
if (__DEV__) {
623
- currentHookNameInDev = 'useLayoutEffect';
784
+ if (currentHookNameInDev !== 'useImperativeHandle') {
785
+ currentHookNameInDev = 'useLayoutEffect';
786
+ }
787
}
788
useEffectImpl(UpdateEffect, UnmountMutation | MountLayout, create, deps);
789
}
816
const prevDeps = prevEffect.deps;
817
if (areHookInputsEqual(nextDeps, prevDeps)) {
818
pushEffect(NoHookEffect, create, destroy, nextDeps);
819
+ currentHookNameInDev = null;
820
return;
821
}
822
}
829
destroy,
830
nextDeps,
831
);
832
+ currentHookNameInDev = null;
833
}
834
835
export function useImperativeHandle<T>(
911
if (nextDeps !== null) {
912
const prevDeps: Array<mixed> | null = prevState[1];
913
if (areHookInputsEqual(nextDeps, prevDeps)) {
914
+ currentHookNameInDev = null;
915
return prevState[0];
916
}
917
}
918
}
919
workInProgressHook.memoizedState = [callback, nextDeps];
920
+ currentHookNameInDev = null;
921
return callback;
922
}
923
939
if (nextDeps !== null) {
940
const prevDeps: Array<mixed> | null = prevState[1];
941
if (areHookInputsEqual(nextDeps, prevDeps)) {
942
+ currentHookNameInDev = null;
943
return prevState[0];
944
}
945
}
950
const nextValue = nextCreate();
951
currentlyRenderingFiber = fiber;
952
workInProgressHook.memoizedState = [nextValue, nextDeps];
953
+ currentHookNameInDev = null;
954
return nextValue;
955
}
956