36
import invariant from 'shared/invariant';
37
import warning from 'shared/warning';
38
import getComponentName from 'shared/getComponentName';
39
-import areHookInputsEqual from 'shared/areHookInputsEqual';
39
+import is from 'shared/objectIs';
40
import {markWorkInProgressReceivedUpdate} from './ReactFiberBeginWork';
41
42
type Update<S, A> = {
68
tag: HookEffectTag,
69
create: () => mixed,
70
destroy: (() => mixed) | null,
71
- inputs: Array<mixed>,
71
+ deps: Array<mixed> | null,
72
next: Effect,
73
};
74
117
let numberOfReRenders: number = -1;
118
const RE_RENDER_LIMIT = 25;
119
120
+// In DEV, this is the name of the currently executing primitive hook
121
+let currentHookNameInDev: ?string;
122
+
123
function resolveCurrentlyRenderingFiber(): Fiber {
124
invariant(
125
currentlyRenderingFiber !== null,
128
return currentlyRenderingFiber;
129
}
130
131
+function areHookInputsEqual(
132
+ nextDeps: Array<mixed>,
133
+ prevDeps: Array<mixed> | null,
134
+) {
135
+ if (prevDeps === null) {
136
+ if (__DEV__) {
137
+ warning(
138
+ false,
139
+ '%s received a final argument during this render, but not during ' +
140
+ 'the previous render. Even though the final argument is optional, ' +
141
+ 'its type cannot change between renders.',
142
+ currentHookNameInDev,
143
+ );
144
+ }
145
+ return false;
146
+ }
147
+
148
+ if (__DEV__) {
149
+ // Don't bother comparing lengths in prod because these arrays should be
150
+ // passed inline.
151
+ if (nextDeps.length !== prevDeps.length) {
152
+ warning(
153
+ false,
154
+ 'The final argument passed to %s changed size between renders. The ' +
155
+ 'order and size of this array must remain constant.\n\n' +
156
+ 'Previous: %s\n' +
157
+ 'Incoming: %s',
158
+ currentHookNameInDev,
159
+ `[${nextDeps.join(', ')}]`,
160
+ `[${prevDeps.join(', ')}]`,
161
+ );
162
+ }
163
+ }
164
+ for (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) {
165
+ if (is(nextDeps[i], prevDeps[i])) {
166
+ continue;
167
+ }
168
+ return false;
169
+ }
170
+ return true;
171
+}
172
+
173
export function renderWithHooks(
174
current: Fiber | null,
175
workInProgress: Fiber,
247
remainingExpirationTime = NoWork;
248
componentUpdateQueue = null;
249
250
+ if (__DEV__) {
251
+ currentHookNameInDev = undefined;
252
+ }
253
+
254
// These were reset above
255
// didScheduleRenderPhaseUpdate = false;
256
// renderPhaseUpdates = null;
296
remainingExpirationTime = NoWork;
297
componentUpdateQueue = null;
298
299
+ if (__DEV__) {
300
+ currentHookNameInDev = undefined;
301
+ }
302
+
303
didScheduleRenderPhaseUpdate = false;
304
renderPhaseUpdates = null;
305
numberOfReRenders = -1;
388
context: ReactContext<T>,
389
observedBits: void | number | boolean,
390
): T {
391
+ if (__DEV__) {
392
+ currentHookNameInDev = 'useContext';
393
+ }
394
// Ensure we're in a function component (class components support only the
395
// .unstable_read() form)
396
resolveCurrentlyRenderingFiber();
400
export function useState<S>(
401
initialState: (() => S) | S,
402
): [S, Dispatch<BasicStateAction<S>>] {
403
+ if (__DEV__) {
404
+ currentHookNameInDev = 'useState';
405
+ }
406
return useReducer(
407
basicStateReducer,
408
// useReducer has a special case to support lazy useState initializers
415
initialState: S,
416
initialAction: A | void | null,
417
): [S, Dispatch<A>] {
418
+ if (__DEV__) {
419
+ if (reducer !== basicStateReducer) {
420
+ currentHookNameInDev = 'useReducer';
421
+ }
422
+ }
423
currentlyRenderingFiber = resolveCurrentlyRenderingFiber();
424
workInProgressHook = createWorkInProgressHook();
425
let queue: UpdateQueue<S, A> | null = (workInProgressHook.queue: any);
564
return [workInProgressHook.memoizedState, dispatch];
565
}
566
503
-function pushEffect(tag, create, destroy, inputs) {
567
+function pushEffect(tag, create, destroy, deps) {
568
const effect: Effect = {
569
tag,
570
create,
571
destroy,
508
- inputs,
572
+ deps,
573
// Circular
574
next: (null: any),
575
};
609
610
export function useLayoutEffect(
611
create: () => mixed,
548
- inputs: Array<mixed> | void | null,
612
+ deps: Array<mixed> | void | null,
613
): void {
550
- useEffectImpl(UpdateEffect, UnmountMutation | MountLayout, create, inputs);
614
+ if (__DEV__) {
615
+ currentHookNameInDev = 'useLayoutEffect';
616
+ }
617
+ useEffectImpl(UpdateEffect, UnmountMutation | MountLayout, create, deps);
618
}
619
620
export function useEffect(
621
create: () => mixed,
555
- inputs: Array<mixed> | void | null,
622
+ deps: Array<mixed> | void | null,
623
): void {
624
+ if (__DEV__) {
625
+ currentHookNameInDev = 'useEffect';
626
+ }
627
useEffectImpl(
628
UpdateEffect | PassiveEffect,
629
UnmountPassive | MountPassive,
630
create,
561
- inputs,
631
+ deps,
632
);
633
}
634
565
-function useEffectImpl(fiberEffectTag, hookEffectTag, create, inputs): void {
635
+function useEffectImpl(fiberEffectTag, hookEffectTag, create, deps): void {
636
currentlyRenderingFiber = resolveCurrentlyRenderingFiber();
637
workInProgressHook = createWorkInProgressHook();
638
569
- let nextInputs = inputs !== undefined && inputs !== null ? inputs : [create];
639
+ const nextDeps = deps === undefined ? null : deps;
640
let destroy = null;
641
if (currentHook !== null) {
642
const prevEffect = currentHook.memoizedState;
643
destroy = prevEffect.destroy;
574
- if (areHookInputsEqual(nextInputs, prevEffect.inputs)) {
575
- pushEffect(NoHookEffect, create, destroy, nextInputs);
576
- return;
644
+ if (nextDeps !== null) {
645
+ const prevDeps = prevEffect.deps;
646
+ if (areHookInputsEqual(nextDeps, prevDeps)) {
647
+ pushEffect(NoHookEffect, create, destroy, nextDeps);
648
+ return;
649
+ }
650
}
651
}
652
655
hookEffectTag,
656
create,
657
destroy,
585
- nextInputs,
658
+ nextDeps,
659
);
660
}
661
662
export function useImperativeHandle<T>(
663
ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,
664
create: () => T,
592
- inputs: Array<mixed> | void | null,
665
+ deps: Array<mixed> | void | null,
666
): void {
594
- // TODO: If inputs are provided, should we skip comparing the ref itself?
595
- const nextInputs =
596
- inputs !== null && inputs !== undefined
597
- ? inputs.concat([ref])
598
- : [ref, create];
667
+ if (__DEV__) {
668
+ currentHookNameInDev = 'useImperativeHandle';
669
+ }
670
+ // TODO: If deps are provided, should we skip comparing the ref itself?
671
+ const nextDeps =
672
+ deps !== null && deps !== undefined ? deps.concat([ref]) : [ref];
673
674
// TODO: I've implemented this on top of useEffect because it's almost the
675
// same thing, and it would require an equal amount of code. It doesn't seem
688
refObject.current = null;
689
};
690
}
617
- }, nextInputs);
691
+ }, nextDeps);
692
}
693
694
export function useDebugValue(
695
value: any,
696
formatterFn: ?(value: any) => any,
697
): void {
698
+ if (__DEV__) {
699
+ currentHookNameInDev = 'useDebugValue';
700
+ }
701
+
702
// This will trigger a warning if the hook is used in a non-Function component.
703
resolveCurrentlyRenderingFiber();
704
709
710
export function useCallback<T>(
711
callback: T,
634
- inputs: Array<mixed> | void | null,
712
+ deps: Array<mixed> | void | null,
713
): T {
714
+ if (__DEV__) {
715
+ currentHookNameInDev = 'useCallback';
716
+ }
717
currentlyRenderingFiber = resolveCurrentlyRenderingFiber();
718
workInProgressHook = createWorkInProgressHook();
719
639
- const nextInputs =
640
- inputs !== undefined && inputs !== null ? inputs : [callback];
720
+ const nextDeps = deps === undefined ? null : deps;
721
722
const prevState = workInProgressHook.memoizedState;
723
if (prevState !== null) {
644
- const prevInputs = prevState[1];
645
- if (areHookInputsEqual(nextInputs, prevInputs)) {
646
- return prevState[0];
724
+ if (nextDeps !== null) {
725
+ const prevDeps: Array<mixed> | null = prevState[1];
726
+ if (areHookInputsEqual(nextDeps, prevDeps)) {
727
+ return prevState[0];
728
+ }
729
}
730
}
649
- workInProgressHook.memoizedState = [callback, nextInputs];
731
+ workInProgressHook.memoizedState = [callback, nextDeps];
732
return callback;
733
}
734
735
export function useMemo<T>(
736
nextCreate: () => T,
655
- inputs: Array<mixed> | void | null,
737
+ deps: Array<mixed> | void | null,
738
): T {
739
+ if (__DEV__) {
740
+ currentHookNameInDev = 'useMemo';
741
+ }
742
currentlyRenderingFiber = resolveCurrentlyRenderingFiber();
743
workInProgressHook = createWorkInProgressHook();
744
660
- const nextInputs =
661
- inputs !== undefined && inputs !== null ? inputs : [nextCreate];
745
+ const nextDeps = deps === undefined ? null : deps;
746
747
const prevState = workInProgressHook.memoizedState;
748
if (prevState !== null) {
665
- const prevInputs = prevState[1];
666
- if (areHookInputsEqual(nextInputs, prevInputs)) {
667
- return prevState[0];
749
+ // Assume these are defined. If they're not, areHookInputsEqual will warn.
750
+ if (nextDeps !== null) {
751
+ const prevDeps: Array<mixed> | null = prevState[1];
752
+ if (areHookInputsEqual(nextDeps, prevDeps)) {
753
+ return prevState[0];
754
+ }
755
}
756
}
757
758
const nextValue = nextCreate();
672
- workInProgressHook.memoizedState = [nextValue, nextInputs];
759
+ workInProgressHook.memoizedState = [nextValue, nextDeps];
760
return nextValue;
761
}
762