8
*/
9
10
import type {ReactContext} from 'shared/ReactTypes';
11
+import type {SideEffectTag} from 'shared/ReactSideEffectTags';
12
import type {Fiber} from './ReactFiber';
13
import type {ExpirationTime} from './ReactFiberExpirationTime';
14
import type {HookEffectTag} from './ReactHookEffectTags';
15
16
+import ReactSharedInternals from 'shared/ReactSharedInternals';
17
+
18
import {NoWork} from './ReactFiberExpirationTime';
16
-import {
17
- readContext,
18
- enterDisallowedContextReadInDEV,
19
- exitDisallowedContextReadInDEV,
20
-} from './ReactFiberNewContext';
19
+import {readContext} from './ReactFiberNewContext';
20
import {
21
Update as UpdateEffect,
22
Passive as PassiveEffect,
41
import is from 'shared/objectIs';
42
import {markWorkInProgressReceivedUpdate} from './ReactFiberBeginWork';
43
44
+const {ReactCurrentDispatcher} = ReactSharedInternals;
45
+
46
+export type Dispatcher = {
47
+ readContext<T>(
48
+ context: ReactContext<T>,
49
+ observedBits: void | number | boolean,
50
+ ): T,
51
+ useState<S>(initialState: (() => S) | S): [S, Dispatch<BasicStateAction<S>>],
52
+ useReducer<S, A>(
53
+ reducer: (S, A) => S,
54
+ initialState: S,
55
+ initialAction: A | void | null,
56
+ ): [S, Dispatch<A>],
57
+ useContext<T>(
58
+ context: ReactContext<T>,
59
+ observedBits: void | number | boolean,
60
+ ): T,
61
+ useRef<T>(initialValue: T): {current: T},
62
+ useEffect(create: () => mixed, deps: Array<mixed> | void | null): void,
63
+ useLayoutEffect(create: () => mixed, deps: Array<mixed> | void | null): void,
64
+ useCallback<T>(callback: T, deps: Array<mixed> | void | null): T,
65
+ useMemo<T>(nextCreate: () => T, deps: Array<mixed> | void | null): T,
66
+ useImperativeHandle<T>(
67
+ ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,
68
+ create: () => T,
69
+ deps: Array<mixed> | void | null,
70
+ ): void,
71
+ useDebugValue<T>(value: T, formatterFn: ?(value: T) => mixed): void,
72
+};
73
+
74
type Update<S, A> = {
75
expirationTime: ExpirationTime,
76
action: A,
101
// the first instance of a hook mismatch in a component,
102
// represented by a portion of its stacktrace
103
let currentHookMismatchInDev = null;
75
-let isInHookUserCodeInDev = false;
104
105
let didWarnAboutMismatchedHooksForComponent;
106
if (__DEV__) {
149
// work-in-progress fiber.
150
let firstCurrentHook: Hook | null = null;
151
let currentHook: Hook | null = null;
152
+let nextCurrentHook: Hook | null = null;
153
let firstWorkInProgressHook: Hook | null = null;
154
let workInProgressHook: Hook | null = null;
155
+let nextWorkInProgressHook: Hook | null = null;
156
157
let remainingExpirationTime: ExpirationTime = NoWork;
158
let componentUpdateQueue: FunctionComponentUpdateQueue | null = null;
159
+let sideEffectTag: SideEffectTag = 0;
160
161
// Updates scheduled during render will trigger an immediate re-render at the
162
// end of the current pass. We can't store these updates on the normal queue,
174
Update<any, any>,
175
> | null = null;
176
// Counter to prevent infinite loops.
146
-let numberOfReRenders: number = -1;
177
+let numberOfReRenders: number = 0;
178
const RE_RENDER_LIMIT = 25;
179
180
// In DEV, this is the name of the currently executing primitive hook
181
let currentHookNameInDev: ?HookType = null;
182
152
-function resolveCurrentlyRenderingFiber(): Fiber {
183
+function throwInvalidHookError() {
184
invariant(
154
- currentlyRenderingFiber !== null,
185
+ false,
186
'Hooks can only be called inside the body of a function component.',
187
);
157
- if (__DEV__) {
158
- // Check if we're inside Hooks like useMemo(). DEV-only for perf.
159
- // TODO: we can make a better warning message with currentHookNameInDev
160
- // if we also make sure it's consistently assigned in the right order.
161
- warning(
162
- !isInHookUserCodeInDev,
163
- 'Hooks can only be called inside the body of a function component. ' +
164
- 'Do not call Hooks inside other Hooks. For more information, see ' +
165
- 'https://fb.me/rules-of-hooks',
166
- );
167
- }
168
- return currentlyRenderingFiber;
188
}
189
190
function areHookInputsEqual(
229
return true;
230
}
231
213
-// till we have String::padEnd, a small function to
214
-// right-pad strings with spaces till a minimum length
215
-function padEndSpaces(string: string, length: number) {
216
- if (__DEV__) {
217
- if (string.length >= length) {
218
- return string;
219
- } else {
220
- return string + ' ' + new Array(length - string.length).join(' ');
221
- }
222
- }
223
-}
224
-
232
function flushHookMismatchWarnings() {
233
// we'll show the diff of the low level hooks,
234
// and a stack trace so the dev can locate where
261
.concat(' Previous render'.length),
262
);
263
264
+ const padEndSpaces = (string, length) => {
265
+ if (string.length >= length) {
266
+ return string;
267
+ }
268
+ return string + ' ' + new Array(length - string.length).join(' ');
269
+ };
270
+
271
let hookStackHeader =
272
((padEndSpaces(' Previous render', columnLength): any): string) +
273
' Next render\n';
323
): any {
324
renderExpirationTime = nextRenderExpirationTime;
325
currentlyRenderingFiber = workInProgress;
312
- firstCurrentHook = current !== null ? current.memoizedState : null;
313
-
314
- if (__DEV__) {
315
- exitDisallowedContextReadInDEV();
316
- isInHookUserCodeInDev = false;
317
- }
326
+ firstCurrentHook = nextCurrentHook =
327
+ current !== null ? current.memoizedState : null;
328
329
// The following should have already been reset
330
// currentHook = null;
335
336
// didScheduleRenderPhaseUpdate = false;
337
// renderPhaseUpdates = null;
328
- // numberOfReRenders = -1;
338
+ // numberOfReRenders = 0;
339
+ // sideEffectTag = 0;
340
330
- let children;
331
- do {
332
- didScheduleRenderPhaseUpdate = false;
333
- numberOfReRenders += 1;
341
+ if (__DEV__) {
342
+ ReactCurrentDispatcher.current =
343
+ nextCurrentHook === null
344
+ ? HooksDispatcherOnMountInDEV
345
+ : HooksDispatcherOnUpdateInDEV;
346
+ } else {
347
+ ReactCurrentDispatcher.current =
348
+ nextCurrentHook === null
349
+ ? HooksDispatcherOnMount
350
+ : HooksDispatcherOnUpdate;
351
+ }
352
335
- // Start over from the beginning of the list
336
- currentHook = null;
337
- workInProgressHook = null;
338
- componentUpdateQueue = null;
353
+ let children = Component(props, refOrContext);
354
340
- children = Component(props, refOrContext);
355
+ if (didScheduleRenderPhaseUpdate) {
356
+ do {
357
+ didScheduleRenderPhaseUpdate = false;
358
+ numberOfReRenders += 1;
359
342
- if (__DEV__) {
343
- if (
344
- current !== null &&
345
- workInProgressHook !== null &&
346
- currentHook === null
347
- ) {
348
- warning(
349
- false,
350
- '%s: Rendered more hooks than during the previous render. This is ' +
351
- 'not currently supported and may lead to unexpected behavior.',
352
- getComponentName(Component),
353
- );
354
- }
355
- flushHookMismatchWarnings();
356
- }
357
- } while (didScheduleRenderPhaseUpdate);
360
+ // Start over from the beginning of the list
361
+ firstCurrentHook = nextCurrentHook =
362
+ current !== null ? current.memoizedState : null;
363
+ nextWorkInProgressHook = firstWorkInProgressHook;
364
359
- renderPhaseUpdates = null;
360
- numberOfReRenders = -1;
365
+ currentHook = null;
366
+ workInProgressHook = null;
367
+ componentUpdateQueue = null;
368
+
369
+ ReactCurrentDispatcher.current = __DEV__
370
+ ? HooksDispatcherOnUpdateInDEV
371
+ : HooksDispatcherOnUpdate;
372
+
373
+ children = Component(props, refOrContext);
374
+ } while (didScheduleRenderPhaseUpdate);
375
+
376
+ renderPhaseUpdates = null;
377
+ numberOfReRenders = 0;
378
+ }
379
+
380
+ if (__DEV__) {
381
+ flushHookMismatchWarnings();
382
+ currentHookNameInDev = null;
383
+ }
384
+
385
+ // We can assume the previous dispatcher is always this one, since we set it
386
+ // at the beginning of the render phase and there's no re-entrancy.
387
+ ReactCurrentDispatcher.current = ContextOnlyDispatcher;
388
389
const renderedWork: Fiber = (currentlyRenderingFiber: any);
390
391
renderedWork.memoizedState = firstWorkInProgressHook;
392
renderedWork.expirationTime = remainingExpirationTime;
366
- renderedWork.updateQueue = componentUpdateQueue;
393
+ renderedWork.updateQueue = (componentUpdateQueue: any);
394
+ renderedWork.effectTag |= sideEffectTag;
395
396
const didRenderTooFewHooks =
397
currentHook !== null && currentHook.next !== null;
401
402
firstCurrentHook = null;
403
currentHook = null;
404
+ nextCurrentHook = null;
405
firstWorkInProgressHook = null;
406
workInProgressHook = null;
407
+ nextWorkInProgressHook = null;
408
409
remainingExpirationTime = NoWork;
410
componentUpdateQueue = null;
381
-
382
- if (__DEV__) {
383
- currentHookNameInDev = null;
384
- }
411
+ sideEffectTag = 0;
412
413
// These were reset above
414
// didScheduleRenderPhaseUpdate = false;
415
// renderPhaseUpdates = null;
389
- // numberOfReRenders = -1;
416
+ // numberOfReRenders = 0;
417
418
invariant(
419
!didRenderTooFewHooks,
439
export function resetHooks(): void {
440
if (__DEV__) {
441
flushHookMismatchWarnings();
415
- exitDisallowedContextReadInDEV();
416
- isInHookUserCodeInDev = false;
442
}
443
444
+ // We can assume the previous dispatcher is always this one, since we set it
445
+ // at the beginning of the render phase and there's no re-entrancy.
446
+ ReactCurrentDispatcher.current = ContextOnlyDispatcher;
447
+
448
// This is used to reset the state of this module when a component throws.
449
// It's also called inside mountIndeterminateComponent if we determine the
450
// component is a module-style component.
453
454
firstCurrentHook = null;
455
currentHook = null;
456
+ nextCurrentHook = null;
457
firstWorkInProgressHook = null;
458
workInProgressHook = null;
459
+ nextWorkInProgressHook = null;
460
461
remainingExpirationTime = NoWork;
462
componentUpdateQueue = null;
463
+ sideEffectTag = 0;
464
465
if (__DEV__) {
466
currentHookNameInDev = null;
468
469
didScheduleRenderPhaseUpdate = false;
470
renderPhaseUpdates = null;
439
- numberOfReRenders = -1;
471
+ numberOfReRenders = 0;
472
}
473
442
-function createHook(): Hook {
443
- let hook: Hook = __DEV__
444
- ? {
445
- _debugType: ((currentHookNameInDev: any): HookType),
446
- memoizedState: null,
474
+function mountWorkInProgressHook(): Hook {
475
+ const hook: Hook = {
476
+ memoizedState: null,
477
448
- baseState: null,
449
- queue: null,
450
- baseUpdate: null,
478
+ baseState: null,
479
+ queue: null,
480
+ baseUpdate: null,
481
452
- next: null,
453
- }
454
- : {
455
- memoizedState: null,
456
-
457
- baseState: null,
458
- queue: null,
459
- baseUpdate: null,
460
-
461
- next: null,
462
- };
482
+ next: null,
483
+ };
484
464
- return hook;
485
+ if (__DEV__) {
486
+ (hook: any)._debugType = (currentHookNameInDev: any);
487
+ if (
488
+ currentlyRenderingFiber !== null &&
489
+ currentlyRenderingFiber.alternate !== null
490
+ ) {
491
+ warning(
492
+ false,
493
+ '%s: Rendered more hooks than during the previous render. This is ' +
494
+ 'not currently supported and may lead to unexpected behavior.',
495
+ getComponentName(currentlyRenderingFiber.type),
496
+ );
497
+ }
498
+ }
499
+ if (workInProgressHook === null) {
500
+ // This is the first hook in the list
501
+ firstWorkInProgressHook = workInProgressHook = hook;
502
+ } else {
503
+ // Append to the end of the list
504
+ workInProgressHook = workInProgressHook.next = hook;
505
+ }
506
+ return workInProgressHook;
507
}
508
467
-function cloneHook(hook: Hook): Hook {
468
- let nextHook: Hook = __DEV__
469
- ? {
470
- _debugType: ((currentHookNameInDev: any): HookType),
471
- memoizedState: hook.memoizedState,
472
-
473
- baseState: hook.baseState,
474
- queue: hook.queue,
475
- baseUpdate: hook.baseUpdate,
509
+function updateWorkInProgressHook(): Hook {
510
+ // This function is used both for updates and for re-renders triggered by a
511
+ // render phase update. It assumes there is either a current hook we can
512
+ // clone, or a work-in-progress hook from a previous render pass that we can
513
+ // use as a base. When we reach the end of the base list, we must switch to
514
+ // the dispatcher used for mounts.
515
+ if (nextWorkInProgressHook !== null) {
516
+ // There's already a work-in-progress. Reuse it.
517
+ workInProgressHook = nextWorkInProgressHook;
518
+ nextWorkInProgressHook = workInProgressHook.next;
519
+
520
+ currentHook = nextCurrentHook;
521
+ nextCurrentHook = currentHook !== null ? currentHook.next : null;
522
+ } else {
523
+ // Clone from the current hook.
524
+ invariant(
525
+ nextCurrentHook !== null,
526
+ 'Rendered more hooks than during the previous render.',
527
+ );
528
+ currentHook = nextCurrentHook;
529
477
- next: null,
478
- }
479
- : {
480
- memoizedState: hook.memoizedState,
530
+ const newHook: Hook = {
531
+ memoizedState: currentHook.memoizedState,
532
482
- baseState: hook.baseState,
483
- queue: hook.queue,
484
- baseUpdate: hook.baseUpdate,
533
+ baseState: currentHook.baseState,
534
+ queue: currentHook.queue,
535
+ baseUpdate: currentHook.baseUpdate,
536
486
- next: null,
487
- };
537
+ next: null,
538
+ };
539
489
- if (__DEV__) {
490
- if (currentHookMismatchInDev === null) {
491
- if (currentHookNameInDev !== ((hook: any): HookDev)._debugType) {
492
- currentHookMismatchInDev = new Error('tracer').stack
493
- .split('\n')
494
- .slice(4)
495
- .join('\n');
540
+ if (__DEV__) {
541
+ (newHook: any)._debugType = (currentHookNameInDev: any);
542
+ if (currentHookMismatchInDev === null) {
543
+ if (currentHookNameInDev !== ((currentHook: any): HookDev)._debugType) {
544
+ currentHookMismatchInDev = new Error('tracer').stack
545
+ .split('\n')
546
+ .slice(4)
547
+ .join('\n');
548
+ }
549
}
550
}
498
- }
499
- return nextHook;
500
-}
551
502
-function createWorkInProgressHook(): Hook {
503
- if (workInProgressHook === null) {
504
- // This is the first hook in the list
505
- if (firstWorkInProgressHook === null) {
506
- currentHook = firstCurrentHook;
507
- if (currentHook === null) {
508
- // This is a newly mounted hook
509
- workInProgressHook = createHook();
510
- } else {
511
- // Clone the current hook.
512
- workInProgressHook = cloneHook(currentHook);
513
- }
514
- firstWorkInProgressHook = workInProgressHook;
515
- } else {
516
- // There's already a work-in-progress. Reuse it.
517
- currentHook = firstCurrentHook;
518
- workInProgressHook = firstWorkInProgressHook;
519
- }
520
- } else {
521
- if (workInProgressHook.next === null) {
522
- let hook;
523
- if (currentHook === null) {
524
- // This is a newly mounted hook
525
- hook = createHook();
526
- } else {
527
- currentHook = currentHook.next;
528
- if (currentHook === null) {
529
- // This is a newly mounted hook
530
- hook = createHook();
531
- } else {
532
- // Clone the current hook.
533
- hook = cloneHook(currentHook);
534
- }
535
- }
536
- // Append to the end of the list
537
- workInProgressHook = workInProgressHook.next = hook;
552
+ if (workInProgressHook === null) {
553
+ // This is the first hook in the list.
554
+ workInProgressHook = firstWorkInProgressHook = newHook;
555
} else {
539
- // There's already a work-in-progress. Reuse it.
540
- workInProgressHook = workInProgressHook.next;
541
- currentHook = currentHook !== null ? currentHook.next : null;
556
+ // Append to the end of the list.
557
+ workInProgressHook = workInProgressHook.next = newHook;
558
}
559
+ nextCurrentHook = currentHook.next;
560
}
561
return workInProgressHook;
562
}
571
return typeof action === 'function' ? action(state) : action;
572
}
573
557
-export function useContext<T>(
574
+function mountContext<T>(
575
context: ReactContext<T>,
576
observedBits: void | number | boolean,
577
): T {
578
if (__DEV__) {
562
- currentHookNameInDev = 'useContext';
563
- createWorkInProgressHook();
564
- currentHookNameInDev = null;
579
+ mountWorkInProgressHook();
580
}
566
- // Ensure we're in a function component (class components support only the
567
- // .unstable_read() form)
568
- resolveCurrentlyRenderingFiber();
581
return readContext(context, observedBits);
582
}
583
572
-export function useState<S>(
573
- initialState: (() => S) | S,
574
-): [S, Dispatch<BasicStateAction<S>>] {
584
+function updateContext<T>(
585
+ context: ReactContext<T>,
586
+ observedBits: void | number | boolean,
587
+): T {
588
if (__DEV__) {
576
- currentHookNameInDev = 'useState';
589
+ updateWorkInProgressHook();
590
}
578
- return useReducer(
579
- basicStateReducer,
580
- // useReducer has a special case to support lazy useState initializers
581
- (initialState: any),
582
- );
591
+ return readContext(context, observedBits);
592
}
593
585
-export function useReducer<S, A>(
594
+function mountReducer<S, A>(
595
reducer: (S, A) => S,
587
- initialState: S,
588
- initialAction: A | void | null,
596
+ initialState: void | S,
597
+ initialAction: void | null | A,
598
): [S, Dispatch<A>] {
590
- if (__DEV__) {
591
- if (reducer !== basicStateReducer) {
592
- currentHookNameInDev = 'useReducer';
593
- }
594
- }
595
- currentlyRenderingFiber = resolveCurrentlyRenderingFiber();
596
- workInProgressHook = createWorkInProgressHook();
597
- if (__DEV__) {
598
- currentHookNameInDev = null;
599
+ const hook = mountWorkInProgressHook();
600
+ // TODO: Lazy init API will change before release.
601
+ if (initialAction !== undefined && initialAction !== null) {
602
+ // $FlowFixMe - Must express with overloading.
603
+ initialState = reducer(initialState, initialAction);
604
}
600
- let queue: UpdateQueue<S, A> | null = (workInProgressHook.queue: any);
601
- if (queue !== null) {
602
- // Already have a queue, so this is an update.
603
- if (numberOfReRenders > 0) {
604
- // This is a re-render. Apply the new render phase updates to the previous
605
- // work-in-progress hook.
606
- const dispatch: Dispatch<A> = (queue.dispatch: any);
607
- if (renderPhaseUpdates !== null) {
608
- // Render phase updates are stored in a map of queue -> linked list
609
- const firstRenderPhaseUpdate = renderPhaseUpdates.get(queue);
610
- if (firstRenderPhaseUpdate !== undefined) {
611
- renderPhaseUpdates.delete(queue);
612
- let newState = workInProgressHook.memoizedState;
613
- let update = firstRenderPhaseUpdate;
614
- do {
615
- // Process this render phase update. We don't have to check the
616
- // priority because it will always be the same as the current
617
- // render's.
618
- const action = update.action;
619
- if (__DEV__) {
620
- enterDisallowedContextReadInDEV();
621
- isInHookUserCodeInDev = true;
622
- }
623
- newState = reducer(newState, action);
624
- if (__DEV__) {
625
- exitDisallowedContextReadInDEV();
626
- isInHookUserCodeInDev = false;
627
- }
628
- update = update.next;
629
- } while (update !== null);
630
-
631
- workInProgressHook.memoizedState = newState;
632
-
633
- // Don't persist the state accumlated from the render phase updates to
634
- // the base state unless the queue is empty.
635
- // TODO: Not sure if this is the desired semantics, but it's what we
636
- // do for gDSFP. I can't remember why.
637
- if (workInProgressHook.baseUpdate === queue.last) {
638
- workInProgressHook.baseState = newState;
639
- }
605
+ hook.memoizedState = hook.baseState = initialState;
606
+ const queue = (hook.queue = {
607
+ last: null,
608
+ dispatch: null,
609
+ eagerReducer: reducer,
610
+ eagerState: (initialState: any),
611
+ });
612
+ const dispatch: Dispatch<A> = (queue.dispatch = (dispatchAction.bind(
613
+ null,
614
+ // Flow doesn't know this is non-null, but we do.
615
+ ((currentlyRenderingFiber: any): Fiber),
616
+ queue,
617
+ ): any));
618
+ return [hook.memoizedState, dispatch];
619
+}
620
641
- return [newState, dispatch];
621
+function updateReducer<S, A>(
622
+ reducer: (S, A) => S,
623
+ initialState: void | S,
624
+ initialAction: void | null | A,
625
+): [S, Dispatch<A>] {
626
+ const hook = updateWorkInProgressHook();
627
+ const queue = hook.queue;
628
+ invariant(
629
+ queue !== null,
630
+ 'Should have a queue. This is likely a bug in React. Please file an issue.',
631
+ );
632
+
633
+ if (numberOfReRenders > 0) {
634
+ // This is a re-render. Apply the new render phase updates to the previous
635
+ // work-in-progress hook.
636
+ const dispatch: Dispatch<A> = (queue.dispatch: any);
637
+ if (renderPhaseUpdates !== null) {
638
+ // Render phase updates are stored in a map of queue -> linked list
639
+ const firstRenderPhaseUpdate = renderPhaseUpdates.get(queue);
640
+ if (firstRenderPhaseUpdate !== undefined) {
641
+ renderPhaseUpdates.delete(queue);
642
+ let newState = hook.memoizedState;
643
+ let update = firstRenderPhaseUpdate;
644
+ do {
645
+ // Process this render phase update. We don't have to check the
646
+ // priority because it will always be the same as the current
647
+ // render's.
648
+ const action = update.action;
649
+ newState = reducer(newState, action);
650
+ update = update.next;
651
+ } while (update !== null);
652
+
653
+ // Mark that the fiber performed work, but only if the new state is
654
+ // different from the current state.
655
+ if (newState !== hook.memoizedState) {
656
+ markWorkInProgressReceivedUpdate();
657
}
658
+
659
+ hook.memoizedState = newState;
660
+
661
+ // Don't persist the state accumlated from the render phase updates to
662
+ // the base state unless the queue is empty.
663
+ // TODO: Not sure if this is the desired semantics, but it's what we
664
+ // do for gDSFP. I can't remember why.
665
+ if (hook.baseUpdate === queue.last) {
666
+ hook.baseState = newState;
667
+ }
668
+
669
+ return [newState, dispatch];
670
}
644
- return [workInProgressHook.memoizedState, dispatch];
671
}
672
+ return [hook.memoizedState, dispatch];
673
+ }
674
647
- // The last update in the entire queue
648
- const last = queue.last;
649
- // The last update that is part of the base state.
650
- const baseUpdate = workInProgressHook.baseUpdate;
651
- const baseState = workInProgressHook.baseState;
652
-
653
- // Find the first unprocessed update.
654
- let first;
655
- if (baseUpdate !== null) {
656
- if (last !== null) {
657
- // For the first update, the queue is a circular linked list where
658
- // `queue.last.next = queue.first`. Once the first update commits, and
659
- // the `baseUpdate` is no longer empty, we can unravel the list.
660
- last.next = null;
661
- }
662
- first = baseUpdate.next;
663
- } else {
664
- first = last !== null ? last.next : null;
675
+ // The last update in the entire queue
676
+ const last = queue.last;
677
+ // The last update that is part of the base state.
678
+ const baseUpdate = hook.baseUpdate;
679
+ const baseState = hook.baseState;
680
+
681
+ // Find the first unprocessed update.
682
+ let first;
683
+ if (baseUpdate !== null) {
684
+ if (last !== null) {
685
+ // For the first update, the queue is a circular linked list where
686
+ // `queue.last.next = queue.first`. Once the first update commits, and
687
+ // the `baseUpdate` is no longer empty, we can unravel the list.
688
+ last.next = null;
689
}
666
- if (first !== null) {
667
- let newState = baseState;
668
- let newBaseState = null;
669
- let newBaseUpdate = null;
670
- let prevUpdate = baseUpdate;
671
- let update = first;
672
- let didSkip = false;
673
- do {
674
- const updateExpirationTime = update.expirationTime;
675
- if (updateExpirationTime < renderExpirationTime) {
676
- // Priority is insufficient. Skip this update. If this is the first
677
- // skipped update, the previous update/state is the new base
678
- // update/state.
679
- if (!didSkip) {
680
- didSkip = true;
681
- newBaseUpdate = prevUpdate;
682
- newBaseState = newState;
683
- }
684
- // Update the remaining priority in the queue.
685
- if (updateExpirationTime > remainingExpirationTime) {
686
- remainingExpirationTime = updateExpirationTime;
687
- }
690
+ first = baseUpdate.next;
691
+ } else {
692
+ first = last !== null ? last.next : null;
693
+ }
694
+ if (first !== null) {
695
+ let newState = baseState;
696
+ let newBaseState = null;
697
+ let newBaseUpdate = null;
698
+ let prevUpdate = baseUpdate;
699
+ let update = first;
700
+ let didSkip = false;
701
+ do {
702
+ const updateExpirationTime = update.expirationTime;
703
+ if (updateExpirationTime < renderExpirationTime) {
704
+ // Priority is insufficient. Skip this update. If this is the first
705
+ // skipped update, the previous update/state is the new base
706
+ // update/state.
707
+ if (!didSkip) {
708
+ didSkip = true;
709
+ newBaseUpdate = prevUpdate;
710
+ newBaseState = newState;
711
+ }
712
+ // Update the remaining priority in the queue.
713
+ if (updateExpirationTime > remainingExpirationTime) {
714
+ remainingExpirationTime = updateExpirationTime;
715
+ }
716
+ } else {
717
+ // Process this update.
718
+ if (update.eagerReducer === reducer) {
719
+ // If this update was processed eagerly, and its reducer matches the
720
+ // current reducer, we can use the eagerly computed state.
721
+ newState = ((update.eagerState: any): S);
722
} else {
689
- // Process this update.
690
- if (update.eagerReducer === reducer) {
691
- // If this update was processed eagerly, and its reducer matches the
692
- // current reducer, we can use the eagerly computed state.
693
- newState = ((update.eagerState: any): S);
694
- } else {
695
- const action = update.action;
696
- if (__DEV__) {
697
- enterDisallowedContextReadInDEV();
698
- isInHookUserCodeInDev = true;
699
- }
700
- newState = reducer(newState, action);
701
- if (__DEV__) {
702
- exitDisallowedContextReadInDEV();
703
- isInHookUserCodeInDev = false;
704
- }
705
- }
723
+ const action = update.action;
724
+ newState = reducer(newState, action);
725
}
707
- prevUpdate = update;
708
- update = update.next;
709
- } while (update !== null && update !== first);
710
-
711
- if (!didSkip) {
712
- newBaseUpdate = prevUpdate;
713
- newBaseState = newState;
726
}
727
+ prevUpdate = update;
728
+ update = update.next;
729
+ } while (update !== null && update !== first);
730
716
- workInProgressHook.memoizedState = newState;
717
- workInProgressHook.baseUpdate = newBaseUpdate;
718
- workInProgressHook.baseState = newBaseState;
719
-
720
- // Mark that the fiber performed work, but only if the new state is
721
- // different from the current state.
722
- if (newState !== (currentHook: any).memoizedState) {
723
- markWorkInProgressReceivedUpdate();
724
- }
725
-
726
- queue.eagerReducer = reducer;
727
- queue.eagerState = newState;
731
+ if (!didSkip) {
732
+ newBaseUpdate = prevUpdate;
733
+ newBaseState = newState;
734
}
735
730
- const dispatch: Dispatch<A> = (queue.dispatch: any);
731
- return [workInProgressHook.memoizedState, dispatch];
732
- }
733
- if (__DEV__) {
734
- enterDisallowedContextReadInDEV();
735
- isInHookUserCodeInDev = true;
736
- }
737
- // There's no existing queue, so this is the initial render.
738
- if (reducer === basicStateReducer) {
739
- // Special case for `useState`.
740
- if (typeof initialState === 'function') {
741
- initialState = initialState();
736
+ // Mark that the fiber performed work, but only if the new state is
737
+ // different from the current state.
738
+ if (newState !== hook.memoizedState) {
739
+ markWorkInProgressReceivedUpdate();
740
}
743
- } else if (initialAction !== undefined && initialAction !== null) {
744
- initialState = reducer(initialState, initialAction);
741
+
742
+ hook.memoizedState = newState;
743
+ hook.baseUpdate = newBaseUpdate;
744
+ hook.baseState = newBaseState;
745
+
746
+ queue.eagerReducer = reducer;
747
+ queue.eagerState = newState;
748
}
746
- if (__DEV__) {
747
- exitDisallowedContextReadInDEV();
748
- isInHookUserCodeInDev = false;
749
+
750
+ const dispatch: Dispatch<A> = (queue.dispatch: any);
751
+ return [hook.memoizedState, dispatch];
752
+}
753
+
754
+function mountState<S>(
755
+ initialState: (() => S) | S,
756
+): [S, Dispatch<BasicStateAction<S>>] {
757
+ const hook = mountWorkInProgressHook();
758
+ // TODO: Lazy init API will change before release.
759
+ if (typeof initialState === 'function') {
760
+ initialState = initialState();
761
}
750
- workInProgressHook.memoizedState = workInProgressHook.baseState = initialState;
751
- queue = workInProgressHook.queue = {
762
+ hook.memoizedState = hook.baseState = initialState;
763
+ const queue = (hook.queue = {
764
last: null,
765
dispatch: null,
754
- eagerReducer: reducer,
755
- eagerState: initialState,
756
- };
757
- const dispatch: Dispatch<A> = (queue.dispatch = (dispatchAction.bind(
766
+ eagerReducer: basicStateReducer,
767
+ eagerState: (initialState: any),
768
+ });
769
+ const dispatch: Dispatch<
770
+ BasicStateAction<S>,
771
+ > = (queue.dispatch = (dispatchAction.bind(
772
null,
759
- currentlyRenderingFiber,
773
+ // Flow doesn't know this is non-null, but we do.
774
+ ((currentlyRenderingFiber: any): Fiber),
775
queue,
776
): any));
762
- return [workInProgressHook.memoizedState, dispatch];
777
+ return [hook.memoizedState, dispatch];
778
+}
779
+
780
+function updateState<S>(
781
+ initialState: (() => S) | S,
782
+): [S, Dispatch<BasicStateAction<S>>] {
783
+ return updateReducer(basicStateReducer, (initialState: any));
784
}
785
786
function pushEffect(tag, create, destroy, deps) {
809
return effect;
810
}
811
791
-export function useRef<T>(initialValue: T): {current: T} {
792
- currentlyRenderingFiber = resolveCurrentlyRenderingFiber();
812
+function mountRef<T>(initialValue: T): {current: T} {
813
+ const hook = mountWorkInProgressHook();
814
+ const ref = {current: initialValue};
815
if (__DEV__) {
794
- currentHookNameInDev = 'useRef';
795
- }
796
- workInProgressHook = createWorkInProgressHook();
797
- if (__DEV__) {
798
- currentHookNameInDev = null;
799
- }
800
- let ref;
801
-
802
- if (workInProgressHook.memoizedState === null) {
803
- ref = {current: initialValue};
804
- if (__DEV__) {
805
- Object.seal(ref);
806
- }
807
- workInProgressHook.memoizedState = ref;
808
- } else {
809
- ref = workInProgressHook.memoizedState;
816
+ Object.seal(ref);
817
}
818
+ hook.memoizedState = ref;
819
return ref;
820
}
821
814
-export function useLayoutEffect(
815
- create: () => mixed,
816
- deps: Array<mixed> | void | null,
817
-): void {
818
- if (__DEV__) {
819
- if (currentHookNameInDev !== 'useImperativeHandle') {
820
- currentHookNameInDev = 'useLayoutEffect';
821
- }
822
- }
823
- useEffectImpl(UpdateEffect, UnmountMutation | MountLayout, create, deps);
822
+function updateRef<T>(initialValue: T): {current: T} {
823
+ const hook = updateWorkInProgressHook();
824
+ return hook.memoizedState;
825
}
826
826
-export function useEffect(
827
- create: () => mixed,
828
- deps: Array<mixed> | void | null,
829
-): void {
830
- if (__DEV__) {
831
- currentHookNameInDev = 'useEffect';
832
- }
833
- useEffectImpl(
834
- UpdateEffect | PassiveEffect,
835
- UnmountPassive | MountPassive,
836
- create,
837
- deps,
838
- );
827
+function mountEffectImpl(fiberEffectTag, hookEffectTag, create, deps): void {
828
+ const hook = mountWorkInProgressHook();
829
+ const nextDeps = deps === undefined ? null : deps;
830
+ sideEffectTag |= fiberEffectTag;
831
+ hook.memoizedState = pushEffect(hookEffectTag, create, null, nextDeps);
832
}
833
841
-function useEffectImpl(fiberEffectTag, hookEffectTag, create, deps): void {
842
- currentlyRenderingFiber = resolveCurrentlyRenderingFiber();
843
- workInProgressHook = createWorkInProgressHook();
844
-
834
+function updateEffectImpl(fiberEffectTag, hookEffectTag, create, deps): void {
835
+ const hook = updateWorkInProgressHook();
836
const nextDeps = deps === undefined ? null : deps;
837
let destroy = null;
838
+
839
if (currentHook !== null) {
840
const prevEffect = currentHook.memoizedState;
841
destroy = prevEffect.destroy;
843
const prevDeps = prevEffect.deps;
844
if (areHookInputsEqual(nextDeps, prevDeps)) {
845
pushEffect(NoHookEffect, create, destroy, nextDeps);
854
- if (__DEV__) {
855
- currentHookNameInDev = null;
856
- }
846
return;
847
}
848
}
849
}
850
862
- currentlyRenderingFiber.effectTag |= fiberEffectTag;
863
- workInProgressHook.memoizedState = pushEffect(
864
- hookEffectTag,
851
+ sideEffectTag |= fiberEffectTag;
852
+ hook.memoizedState = pushEffect(hookEffectTag, create, destroy, nextDeps);
853
+}
854
+
855
+function mountEffect(
856
+ create: () => mixed,
857
+ deps: Array<mixed> | void | null,
858
+): void {
859
+ return mountEffectImpl(
860
+ UpdateEffect | PassiveEffect,
861
+ UnmountPassive | MountPassive,
862
create,
866
- destroy,
867
- nextDeps,
863
+ deps,
864
);
869
- if (__DEV__) {
870
- currentHookNameInDev = null;
865
+}
866
+
867
+function updateEffect(
868
+ create: () => mixed,
869
+ deps: Array<mixed> | void | null,
870
+): void {
871
+ return updateEffectImpl(
872
+ UpdateEffect | PassiveEffect,
873
+ UnmountPassive | MountPassive,
874
+ create,
875
+ deps,
876
+ );
877
+}
878
+
879
+function mountLayoutEffect(
880
+ create: () => mixed,
881
+ deps: Array<mixed> | void | null,
882
+): void {
883
+ return mountEffectImpl(
884
+ UpdateEffect,
885
+ UnmountMutation | MountLayout,
886
+ create,
887
+ deps,
888
+ );
889
+}
890
+
891
+function updateLayoutEffect(
892
+ create: () => mixed,
893
+ deps: Array<mixed> | void | null,
894
+): void {
895
+ return updateEffectImpl(
896
+ UpdateEffect,
897
+ UnmountMutation | MountLayout,
898
+ create,
899
+ deps,
900
+ );
901
+}
902
+
903
+function imperativeHandleEffect<T>(
904
+ create: () => T,
905
+ ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,
906
+) {
907
+ if (typeof ref === 'function') {
908
+ const refCallback = ref;
909
+ const inst = create();
910
+ refCallback(inst);
911
+ return () => refCallback(null);
912
+ } else if (ref !== null && ref !== undefined) {
913
+ const refObject = ref;
914
+ if (__DEV__) {
915
+ warning(
916
+ refObject.hasOwnProperty('current'),
917
+ 'Expected useImperativeHandle() first argument to either be a ' +
918
+ 'ref callback or React.createRef() object. Instead received: %s.',
919
+ 'an object with keys {' + Object.keys(refObject).join(', ') + '}',
920
+ );
921
+ }
922
+ const inst = create();
923
+ refObject.current = inst;
924
+ return () => {
925
+ refObject.current = null;
926
+ };
927
}
928
}
929
874
-export function useImperativeHandle<T>(
930
+function mountImperativeHandle<T>(
931
ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,
932
create: () => T,
933
deps: Array<mixed> | void | null,
934
): void {
935
if (__DEV__) {
880
- currentHookNameInDev = 'useImperativeHandle';
936
warning(
937
typeof create === 'function',
938
'Expected useImperativeHandle() second argument to be a function ' +
940
create !== null ? typeof create : 'null',
941
);
942
}
943
+
944
// TODO: If deps are provided, should we skip comparing the ref itself?
889
- const nextDeps =
945
+ const effectDeps =
946
deps !== null && deps !== undefined ? deps.concat([ref]) : [ref];
947
892
- // TODO: I've implemented this on top of useEffect because it's almost the
893
- // same thing, and it would require an equal amount of code. It doesn't seem
894
- // like a common enough use case to justify the additional size.
895
- useLayoutEffect(() => {
896
- if (typeof ref === 'function') {
897
- const refCallback = ref;
898
- const inst = create();
899
- refCallback(inst);
900
- return () => refCallback(null);
901
- } else if (ref !== null && ref !== undefined) {
902
- const refObject = ref;
903
- if (__DEV__) {
904
- warning(
905
- refObject.hasOwnProperty('current'),
906
- 'Expected useImperativeHandle() first argument to either be a ' +
907
- 'ref callback or React.createRef() object. Instead received: %s.',
908
- 'an object with keys {' + Object.keys(refObject).join(', ') + '}',
909
- );
910
- }
911
- const inst = create();
912
- refObject.current = inst;
913
- return () => {
914
- refObject.current = null;
915
- };
916
- }
917
- }, nextDeps);
948
+ return mountEffectImpl(
949
+ UpdateEffect,
950
+ UnmountMutation | MountLayout,
951
+ imperativeHandleEffect.bind(null, create, ref),
952
+ effectDeps,
953
+ );
954
}
955
920
-export function useDebugValue(
921
- value: any,
922
- formatterFn: ?(value: any) => any,
956
+function updateImperativeHandle<T>(
957
+ ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,
958
+ create: () => T,
959
+ deps: Array<mixed> | void | null,
960
): void {
961
if (__DEV__) {
925
- currentHookNameInDev = 'useDebugValue';
962
+ warning(
963
+ typeof create === 'function',
964
+ 'Expected useImperativeHandle() second argument to be a function ' +
965
+ 'that creates a handle. Instead received: %s.',
966
+ create !== null ? typeof create : 'null',
967
+ );
968
}
969
928
- // This will trigger a warning if the hook is used in a non-Function component.
929
- resolveCurrentlyRenderingFiber();
970
+ // TODO: If deps are provided, should we skip comparing the ref itself?
971
+ const effectDeps =
972
+ deps !== null && deps !== undefined ? deps.concat([ref]) : [ref];
973
+
974
+ return updateEffectImpl(
975
+ UpdateEffect,
976
+ UnmountMutation | MountLayout,
977
+ imperativeHandleEffect.bind(null, create, ref),
978
+ effectDeps,
979
+ );
980
+}
981
982
+function mountDebugValue<T>(value: T, formatterFn: ?(value: T) => mixed): void {
983
// This hook is normally a no-op.
984
// The react-debug-hooks package injects its own implementation
985
// so that e.g. DevTools can display custom hook values.
986
}
987
936
-export function useCallback<T>(
937
- callback: T,
938
- deps: Array<mixed> | void | null,
939
-): T {
940
- if (__DEV__) {
941
- currentHookNameInDev = 'useCallback';
942
- }
943
- currentlyRenderingFiber = resolveCurrentlyRenderingFiber();
944
- workInProgressHook = createWorkInProgressHook();
988
+const updateDebugValue = mountDebugValue;
989
990
+function mountCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
991
+ const hook = mountWorkInProgressHook();
992
const nextDeps = deps === undefined ? null : deps;
993
+ hook.memoizedState = [callback, nextDeps];
994
+ return callback;
995
+}
996
948
- const prevState = workInProgressHook.memoizedState;
997
+function updateCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
998
+ const hook = updateWorkInProgressHook();
999
+ const nextDeps = deps === undefined ? null : deps;
1000
+ const prevState = hook.memoizedState;
1001
if (prevState !== null) {
1002
if (nextDeps !== null) {
1003
const prevDeps: Array<mixed> | null = prevState[1];
1004
if (areHookInputsEqual(nextDeps, prevDeps)) {
953
- currentHookNameInDev = null;
1005
return prevState[0];
1006
}
1007
}
1008
}
958
- workInProgressHook.memoizedState = [callback, nextDeps];
959
- if (__DEV__) {
960
- currentHookNameInDev = null;
961
- }
1009
+ hook.memoizedState = [callback, nextDeps];
1010
return callback;
1011
}
1012
965
-export function useMemo<T>(
1013
+function mountMemo<T>(
1014
nextCreate: () => T,
1015
deps: Array<mixed> | void | null,
1016
): T {
969
- if (__DEV__) {
970
- currentHookNameInDev = 'useMemo';
971
- }
972
- currentlyRenderingFiber = resolveCurrentlyRenderingFiber();
973
- workInProgressHook = createWorkInProgressHook();
974
-
1017
+ const hook = mountWorkInProgressHook();
1018
const nextDeps = deps === undefined ? null : deps;
1019
+ const nextValue = nextCreate();
1020
+ hook.memoizedState = [nextValue, nextDeps];
1021
+ return nextValue;
1022
+}
1023
977
- const prevState = workInProgressHook.memoizedState;
1024
+function updateMemo<T>(
1025
+ nextCreate: () => T,
1026
+ deps: Array<mixed> | void | null,
1027
+): T {
1028
+ const hook = updateWorkInProgressHook();
1029
+ const nextDeps = deps === undefined ? null : deps;
1030
+ const prevState = hook.memoizedState;
1031
if (prevState !== null) {
1032
// Assume these are defined. If they're not, areHookInputsEqual will warn.
1033
if (nextDeps !== null) {
1034
const prevDeps: Array<mixed> | null = prevState[1];
1035
if (areHookInputsEqual(nextDeps, prevDeps)) {
983
- if (__DEV__) {
984
- currentHookNameInDev = null;
985
- }
1036
return prevState[0];
1037
}
1038
}
1039
}
990
-
991
- if (__DEV__) {
992
- enterDisallowedContextReadInDEV();
993
- isInHookUserCodeInDev = true;
994
- }
1040
const nextValue = nextCreate();
996
- if (__DEV__) {
997
- exitDisallowedContextReadInDEV();
998
- isInHookUserCodeInDev = false;
999
- }
1000
- workInProgressHook.memoizedState = [nextValue, nextDeps];
1001
- if (__DEV__) {
1002
- currentHookNameInDev = null;
1003
- }
1041
+ hook.memoizedState = [nextValue, nextDeps];
1042
return nextValue;
1043
}
1044
1130
// same as the current state, we may be able to bail out entirely.
1131
const eagerReducer = queue.eagerReducer;
1132
if (eagerReducer !== null) {
1133
+ let prevDispatcher;
1134
+ if (__DEV__) {
1135
+ prevDispatcher = ReactCurrentDispatcher.current;
1136
+ ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
1137
+ }
1138
try {
1139
const currentState: S = (queue.eagerState: any);
1097
- if (__DEV__) {
1098
- enterDisallowedContextReadInDEV();
1099
- isInHookUserCodeInDev = true;
1100
- }
1140
const eagerState = eagerReducer(currentState, action);
1102
- if (__DEV__) {
1103
- exitDisallowedContextReadInDEV();
1104
- isInHookUserCodeInDev = false;
1105
- }
1141
// Stash the eagerly computed state, and the reducer used to compute
1142
// it, on the update object. If the reducer hasn't changed by the
1143
// time we enter the render phase, then the eager state can be used
1155
// Suppress the error. It will throw again in the render phase.
1156
} finally {
1157
if (__DEV__) {
1123
- exitDisallowedContextReadInDEV();
1124
- isInHookUserCodeInDev = false;
1158
+ ReactCurrentDispatcher.current = prevDispatcher;
1159
}
1160
}
1161
}
1163
scheduleWork(fiber, expirationTime);
1164
}
1165
}
1166
+
1167
+export const ContextOnlyDispatcher: Dispatcher = {
1168
+ readContext,
1169
+
1170
+ useCallback: throwInvalidHookError,
1171
+ useContext: throwInvalidHookError,
1172
+ useEffect: throwInvalidHookError,
1173
+ useImperativeHandle: throwInvalidHookError,
1174
+ useLayoutEffect: throwInvalidHookError,
1175
+ useMemo: throwInvalidHookError,
1176
+ useReducer: throwInvalidHookError,
1177
+ useRef: throwInvalidHookError,
1178
+ useState: throwInvalidHookError,
1179
+ useDebugValue: throwInvalidHookError,
1180
+};
1181
+
1182
+const HooksDispatcherOnMount: Dispatcher = {
1183
+ readContext,
1184
+
1185
+ useCallback: mountCallback,
1186
+ useContext: readContext,
1187
+ useEffect: mountEffect,
1188
+ useImperativeHandle: mountImperativeHandle,
1189
+ useLayoutEffect: mountLayoutEffect,
1190
+ useMemo: mountMemo,
1191
+ useReducer: mountReducer,
1192
+ useRef: mountRef,
1193
+ useState: mountState,
1194
+ useDebugValue: mountDebugValue,
1195
+};
1196
+
1197
+const HooksDispatcherOnUpdate: Dispatcher = {
1198
+ readContext,
1199
+
1200
+ useCallback: updateCallback,
1201
+ useContext: readContext,
1202
+ useEffect: updateEffect,
1203
+ useImperativeHandle: updateImperativeHandle,
1204
+ useLayoutEffect: updateLayoutEffect,
1205
+ useMemo: updateMemo,
1206
+ useReducer: updateReducer,
1207
+ useRef: updateRef,
1208
+ useState: updateState,
1209
+ useDebugValue: updateDebugValue,
1210
+};
1211
+
1212
+let HooksDispatcherOnMountInDEV: Dispatcher | null = null;
1213
+let HooksDispatcherOnUpdateInDEV: Dispatcher | null = null;
1214
+let InvalidNestedHooksDispatcherOnMountInDEV: Dispatcher | null = null;
1215
+let InvalidNestedHooksDispatcherOnUpdateInDEV: Dispatcher | null = null;
1216
+
1217
+if (__DEV__) {
1218
+ const warnInvalidContextAccess = () => {
1219
+ warning(
1220
+ false,
1221
+ 'Context can only be read while React is rendering. ' +
1222
+ 'In classes, you can read it in the render method or getDerivedStateFromProps. ' +
1223
+ 'In function components, you can read it directly in the function body, but not ' +
1224
+ 'inside Hooks like useReducer() or useMemo().',
1225
+ );
1226
+ };
1227
+
1228
+ const warnInvalidHookAccess = () => {
1229
+ warning(
1230
+ false,
1231
+ 'Hooks can only be called inside the body of a function component. ' +
1232
+ 'Do not call Hooks inside other Hooks. For more information, see ' +
1233
+ 'https://fb.me/rules-of-hooks',
1234
+ );
1235
+ };
1236
+
1237
+ HooksDispatcherOnMountInDEV = {
1238
+ readContext<T>(
1239
+ context: ReactContext<T>,
1240
+ observedBits: void | number | boolean,
1241
+ ): T {
1242
+ return readContext(context, observedBits);
1243
+ },
1244
+
1245
+ useCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
1246
+ currentHookNameInDev = 'useCallback';
1247
+ return mountCallback(callback, deps);
1248
+ },
1249
+ useContext<T>(
1250
+ context: ReactContext<T>,
1251
+ observedBits: void | number | boolean,
1252
+ ): T {
1253
+ currentHookNameInDev = 'useContext';
1254
+ return mountContext(context, observedBits);
1255
+ },
1256
+ useEffect(create: () => mixed, deps: Array<mixed> | void | null): void {
1257
+ currentHookNameInDev = 'useEffect';
1258
+ return mountEffect(create, deps);
1259
+ },
1260
+ useImperativeHandle<T>(
1261
+ ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,
1262
+ create: () => T,
1263
+ deps: Array<mixed> | void | null,
1264
+ ): void {
1265
+ currentHookNameInDev = 'useImperativeHandle';
1266
+ return mountImperativeHandle(ref, create, deps);
1267
+ },
1268
+ useLayoutEffect(
1269
+ create: () => mixed,
1270
+ deps: Array<mixed> | void | null,
1271
+ ): void {
1272
+ currentHookNameInDev = 'useLayoutEffect';
1273
+ return mountLayoutEffect(create, deps);
1274
+ },
1275
+ useMemo<T>(create: () => T, deps: Array<mixed> | void | null): T {
1276
+ currentHookNameInDev = 'useMemo';
1277
+ const prevDispatcher = ReactCurrentDispatcher.current;
1278
+ ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnMountInDEV;
1279
+ try {
1280
+ return mountMemo(create, deps);
1281
+ } finally {
1282
+ ReactCurrentDispatcher.current = prevDispatcher;
1283
+ }
1284
+ },
1285
+ useReducer<S, A>(
1286
+ reducer: (S, A) => S,
1287
+ initialState: S,
1288
+ initialAction: A | void | null,
1289
+ ): [S, Dispatch<A>] {
1290
+ currentHookNameInDev = 'useReducer';
1291
+ const prevDispatcher = ReactCurrentDispatcher.current;
1292
+ ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnMountInDEV;
1293
+ try {
1294
+ return mountReducer(reducer, initialState, initialAction);
1295
+ } finally {
1296
+ ReactCurrentDispatcher.current = prevDispatcher;
1297
+ }
1298
+ },
1299
+ useRef<T>(initialValue: T): {current: T} {
1300
+ currentHookNameInDev = 'useRef';
1301
+ return mountRef(initialValue);
1302
+ },
1303
+ useState<S>(
1304
+ initialState: (() => S) | S,
1305
+ ): [S, Dispatch<BasicStateAction<S>>] {
1306
+ currentHookNameInDev = 'useState';
1307
+ const prevDispatcher = ReactCurrentDispatcher.current;
1308
+ ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnMountInDEV;
1309
+ try {
1310
+ return mountState(initialState);
1311
+ } finally {
1312
+ ReactCurrentDispatcher.current = prevDispatcher;
1313
+ }
1314
+ },
1315
+ useDebugValue<T>(value: T, formatterFn: ?(value: T) => mixed): void {
1316
+ currentHookNameInDev = 'useDebugValue';
1317
+ return mountDebugValue(value, formatterFn);
1318
+ },
1319
+ };
1320
+
1321
+ HooksDispatcherOnUpdateInDEV = {
1322
+ readContext<T>(
1323
+ context: ReactContext<T>,
1324
+ observedBits: void | number | boolean,
1325
+ ): T {
1326
+ return readContext(context, observedBits);
1327
+ },
1328
+
1329
+ useCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
1330
+ currentHookNameInDev = 'useCallback';
1331
+ return updateCallback(callback, deps);
1332
+ },
1333
+ useContext<T>(
1334
+ context: ReactContext<T>,
1335
+ observedBits: void | number | boolean,
1336
+ ): T {
1337
+ currentHookNameInDev = 'useContext';
1338
+ return updateContext(context, observedBits);
1339
+ },
1340
+ useEffect(create: () => mixed, deps: Array<mixed> | void | null): void {
1341
+ currentHookNameInDev = 'useEffect';
1342
+ return updateEffect(create, deps);
1343
+ },
1344
+ useImperativeHandle<T>(
1345
+ ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,
1346
+ create: () => T,
1347
+ deps: Array<mixed> | void | null,
1348
+ ): void {
1349
+ currentHookNameInDev = 'useImperativeHandle';
1350
+ return updateImperativeHandle(ref, create, deps);
1351
+ },
1352
+ useLayoutEffect(
1353
+ create: () => mixed,
1354
+ deps: Array<mixed> | void | null,
1355
+ ): void {
1356
+ currentHookNameInDev = 'useLayoutEffect';
1357
+ return updateLayoutEffect(create, deps);
1358
+ },
1359
+ useMemo<T>(create: () => T, deps: Array<mixed> | void | null): T {
1360
+ currentHookNameInDev = 'useMemo';
1361
+ const prevDispatcher = ReactCurrentDispatcher.current;
1362
+ ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
1363
+ try {
1364
+ return updateMemo(create, deps);
1365
+ } finally {
1366
+ ReactCurrentDispatcher.current = prevDispatcher;
1367
+ }
1368
+ },
1369
+ useReducer<S, A>(
1370
+ reducer: (S, A) => S,
1371
+ initialState: S,
1372
+ initialAction: A | void | null,
1373
+ ): [S, Dispatch<A>] {
1374
+ currentHookNameInDev = 'useReducer';
1375
+ const prevDispatcher = ReactCurrentDispatcher.current;
1376
+ ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
1377
+ try {
1378
+ return updateReducer(reducer, initialState, initialAction);
1379
+ } finally {
1380
+ ReactCurrentDispatcher.current = prevDispatcher;
1381
+ }
1382
+ },
1383
+ useRef<T>(initialValue: T): {current: T} {
1384
+ currentHookNameInDev = 'useRef';
1385
+ return updateRef(initialValue);
1386
+ },
1387
+ useState<S>(
1388
+ initialState: (() => S) | S,
1389
+ ): [S, Dispatch<BasicStateAction<S>>] {
1390
+ currentHookNameInDev = 'useState';
1391
+ const prevDispatcher = ReactCurrentDispatcher.current;
1392
+ ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
1393
+ try {
1394
+ return updateState(initialState);
1395
+ } finally {
1396
+ ReactCurrentDispatcher.current = prevDispatcher;
1397
+ }
1398
+ },
1399
+ useDebugValue<T>(value: T, formatterFn: ?(value: T) => mixed): void {
1400
+ currentHookNameInDev = 'useDebugValue';
1401
+ return updateDebugValue(value, formatterFn);
1402
+ },
1403
+ };
1404
+
1405
+ InvalidNestedHooksDispatcherOnMountInDEV = {
1406
+ readContext<T>(
1407
+ context: ReactContext<T>,
1408
+ observedBits: void | number | boolean,
1409
+ ): T {
1410
+ warnInvalidContextAccess();
1411
+ return readContext(context, observedBits);
1412
+ },
1413
+
1414
+ useCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
1415
+ currentHookNameInDev = 'useCallback';
1416
+ warnInvalidHookAccess();
1417
+ return mountCallback(callback, deps);
1418
+ },
1419
+ useContext<T>(
1420
+ context: ReactContext<T>,
1421
+ observedBits: void | number | boolean,
1422
+ ): T {
1423
+ currentHookNameInDev = 'useContext';
1424
+ warnInvalidHookAccess();
1425
+ return mountContext(context, observedBits);
1426
+ },
1427
+ useEffect(create: () => mixed, deps: Array<mixed> | void | null): void {
1428
+ currentHookNameInDev = 'useEffect';
1429
+ warnInvalidHookAccess();
1430
+ return mountEffect(create, deps);
1431
+ },
1432
+ useImperativeHandle<T>(
1433
+ ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,
1434
+ create: () => T,
1435
+ deps: Array<mixed> | void | null,
1436
+ ): void {
1437
+ currentHookNameInDev = 'useImperativeHandle';
1438
+ warnInvalidHookAccess();
1439
+ return mountImperativeHandle(ref, create, deps);
1440
+ },
1441
+ useLayoutEffect(
1442
+ create: () => mixed,
1443
+ deps: Array<mixed> | void | null,
1444
+ ): void {
1445
+ currentHookNameInDev = 'useLayoutEffect';
1446
+ warnInvalidHookAccess();
1447
+ return mountLayoutEffect(create, deps);
1448
+ },
1449
+ useMemo<T>(create: () => T, deps: Array<mixed> | void | null): T {
1450
+ currentHookNameInDev = 'useMemo';
1451
+ warnInvalidHookAccess();
1452
+ const prevDispatcher = ReactCurrentDispatcher.current;
1453
+ ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnMountInDEV;
1454
+ try {
1455
+ return mountMemo(create, deps);
1456
+ } finally {
1457
+ ReactCurrentDispatcher.current = prevDispatcher;
1458
+ }
1459
+ },
1460
+ useReducer<S, A>(
1461
+ reducer: (S, A) => S,
1462
+ initialState: S,
1463
+ initialAction: A | void | null,
1464
+ ): [S, Dispatch<A>] {
1465
+ currentHookNameInDev = 'useReducer';
1466
+ warnInvalidHookAccess();
1467
+ const prevDispatcher = ReactCurrentDispatcher.current;
1468
+ ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnMountInDEV;
1469
+ try {
1470
+ return mountReducer(reducer, initialState, initialAction);
1471
+ } finally {
1472
+ ReactCurrentDispatcher.current = prevDispatcher;
1473
+ }
1474
+ },
1475
+ useRef<T>(initialValue: T): {current: T} {
1476
+ currentHookNameInDev = 'useRef';
1477
+ warnInvalidHookAccess();
1478
+ return mountRef(initialValue);
1479
+ },
1480
+ useState<S>(
1481
+ initialState: (() => S) | S,
1482
+ ): [S, Dispatch<BasicStateAction<S>>] {
1483
+ currentHookNameInDev = 'useState';
1484
+ warnInvalidHookAccess();
1485
+ const prevDispatcher = ReactCurrentDispatcher.current;
1486
+ ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnMountInDEV;
1487
+ try {
1488
+ return mountState(initialState);
1489
+ } finally {
1490
+ ReactCurrentDispatcher.current = prevDispatcher;
1491
+ }
1492
+ },
1493
+ useDebugValue<T>(value: T, formatterFn: ?(value: T) => mixed): void {
1494
+ currentHookNameInDev = 'useDebugValue';
1495
+ warnInvalidHookAccess();
1496
+ return mountDebugValue(value, formatterFn);
1497
+ },
1498
+ };
1499
+
1500
+ InvalidNestedHooksDispatcherOnUpdateInDEV = {
1501
+ readContext<T>(
1502
+ context: ReactContext<T>,
1503
+ observedBits: void | number | boolean,
1504
+ ): T {
1505
+ warnInvalidContextAccess();
1506
+ return readContext(context, observedBits);
1507
+ },
1508
+
1509
+ useCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
1510
+ currentHookNameInDev = 'useCallback';
1511
+ warnInvalidHookAccess();
1512
+ return updateCallback(callback, deps);
1513
+ },
1514
+ useContext<T>(
1515
+ context: ReactContext<T>,
1516
+ observedBits: void | number | boolean,
1517
+ ): T {
1518
+ currentHookNameInDev = 'useContext';
1519
+ warnInvalidHookAccess();
1520
+ return updateContext(context, observedBits);
1521
+ },
1522
+ useEffect(create: () => mixed, deps: Array<mixed> | void | null): void {
1523
+ currentHookNameInDev = 'useEffect';
1524
+ warnInvalidHookAccess();
1525
+ return updateEffect(create, deps);
1526
+ },
1527
+ useImperativeHandle<T>(
1528
+ ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,
1529
+ create: () => T,
1530
+ deps: Array<mixed> | void | null,
1531
+ ): void {
1532
+ currentHookNameInDev = 'useImperativeHandle';
1533
+ warnInvalidHookAccess();
1534
+ return updateImperativeHandle(ref, create, deps);
1535
+ },
1536
+ useLayoutEffect(
1537
+ create: () => mixed,
1538
+ deps: Array<mixed> | void | null,
1539
+ ): void {
1540
+ currentHookNameInDev = 'useLayoutEffect';
1541
+ warnInvalidHookAccess();
1542
+ return updateLayoutEffect(create, deps);
1543
+ },
1544
+ useMemo<T>(create: () => T, deps: Array<mixed> | void | null): T {
1545
+ currentHookNameInDev = 'useMemo';
1546
+ warnInvalidHookAccess();
1547
+ const prevDispatcher = ReactCurrentDispatcher.current;
1548
+ ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
1549
+ try {
1550
+ return updateMemo(create, deps);
1551
+ } finally {
1552
+ ReactCurrentDispatcher.current = prevDispatcher;
1553
+ }
1554
+ },
1555
+ useReducer<S, A>(
1556
+ reducer: (S, A) => S,
1557
+ initialState: S,
1558
+ initialAction: A | void | null,
1559
+ ): [S, Dispatch<A>] {
1560
+ currentHookNameInDev = 'useReducer';
1561
+ warnInvalidHookAccess();
1562
+ const prevDispatcher = ReactCurrentDispatcher.current;
1563
+ ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
1564
+ try {
1565
+ return updateReducer(reducer, initialState, initialAction);
1566
+ } finally {
1567
+ ReactCurrentDispatcher.current = prevDispatcher;
1568
+ }
1569
+ },
1570
+ useRef<T>(initialValue: T): {current: T} {
1571
+ currentHookNameInDev = 'useRef';
1572
+ warnInvalidHookAccess();
1573
+ return updateRef(initialValue);
1574
+ },
1575
+ useState<S>(
1576
+ initialState: (() => S) | S,
1577
+ ): [S, Dispatch<BasicStateAction<S>>] {
1578
+ currentHookNameInDev = 'useState';
1579
+ warnInvalidHookAccess();
1580
+ const prevDispatcher = ReactCurrentDispatcher.current;
1581
+ ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
1582
+ try {
1583
+ return updateState(initialState);
1584
+ } finally {
1585
+ ReactCurrentDispatcher.current = prevDispatcher;
1586
+ }
1587
+ },
1588
+ useDebugValue<T>(value: T, formatterFn: ?(value: T) => mixed): void {
1589
+ currentHookNameInDev = 'useDebugValue';
1590
+ warnInvalidHookAccess();
1591
+ return updateDebugValue(value, formatterFn);
1592
+ },
1593
+ };
1594
+}