Skip to content

Commit fd557d4

Browse files
authored
Warn on mount when deps are not an array (#15018)
* Warn on mount when deps are not an array * Check other Hooks * I can't figure out how to fix error/warning nesting lint But it doesn't really matter much because we test other cases in the other test.
1 parent ff596e3 commit fd557d4

File tree

2 files changed

+92
-0
lines changed

2 files changed

+92
-0
lines changed

packages/react-reconciler/src/ReactFiberHooks.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,22 @@ function updateHookTypesDev() {
209209
}
210210
}
211211

212+
function checkDepsAreArrayDev(deps: mixed) {
213+
if (__DEV__) {
214+
if (deps !== undefined && deps !== null && !Array.isArray(deps)) {
215+
// Verify deps, but only on mount to avoid extra checks.
216+
// It's unlikely their type would change as usually you define them inline.
217+
warning(
218+
false,
219+
'%s received a final argument that is not an array (instead, received `%s`). When ' +
220+
'specified, the final argument must be an array.',
221+
currentHookNameInDev,
222+
typeof deps,
223+
);
224+
}
225+
}
226+
}
227+
212228
function warnOnHookMismatchInDev(currentHookName: HookType) {
213229
if (__DEV__) {
214230
const componentName = getComponentName(
@@ -1249,6 +1265,7 @@ if (__DEV__) {
12491265
useCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
12501266
currentHookNameInDev = 'useCallback';
12511267
mountHookTypesDev();
1268+
checkDepsAreArrayDev(deps);
12521269
return mountCallback(callback, deps);
12531270
},
12541271
useContext<T>(
@@ -1265,6 +1282,7 @@ if (__DEV__) {
12651282
): void {
12661283
currentHookNameInDev = 'useEffect';
12671284
mountHookTypesDev();
1285+
checkDepsAreArrayDev(deps);
12681286
return mountEffect(create, deps);
12691287
},
12701288
useImperativeHandle<T>(
@@ -1274,6 +1292,7 @@ if (__DEV__) {
12741292
): void {
12751293
currentHookNameInDev = 'useImperativeHandle';
12761294
mountHookTypesDev();
1295+
checkDepsAreArrayDev(deps);
12771296
return mountImperativeHandle(ref, create, deps);
12781297
},
12791298
useLayoutEffect(
@@ -1282,11 +1301,13 @@ if (__DEV__) {
12821301
): void {
12831302
currentHookNameInDev = 'useLayoutEffect';
12841303
mountHookTypesDev();
1304+
checkDepsAreArrayDev(deps);
12851305
return mountLayoutEffect(create, deps);
12861306
},
12871307
useMemo<T>(create: () => T, deps: Array<mixed> | void | null): T {
12881308
currentHookNameInDev = 'useMemo';
12891309
mountHookTypesDev();
1310+
checkDepsAreArrayDev(deps);
12901311
const prevDispatcher = ReactCurrentDispatcher.current;
12911312
ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnMountInDEV;
12921313
try {

packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -632,6 +632,77 @@ describe('ReactHooks', () => {
632632
]);
633633
});
634634

635+
it('warns if deps is not an array', () => {
636+
const {useEffect, useLayoutEffect, useMemo, useCallback} = React;
637+
638+
function App(props) {
639+
useEffect(() => {}, props.deps);
640+
useLayoutEffect(() => {}, props.deps);
641+
useMemo(() => {}, props.deps);
642+
useCallback(() => {}, props.deps);
643+
return null;
644+
}
645+
646+
expect(() => {
647+
ReactTestRenderer.create(<App deps={'hello'} />);
648+
}).toWarnDev([
649+
'Warning: useEffect received a final argument that is not an array (instead, received `string`). ' +
650+
'When specified, the final argument must be an array.',
651+
'Warning: useLayoutEffect received a final argument that is not an array (instead, received `string`). ' +
652+
'When specified, the final argument must be an array.',
653+
'Warning: useMemo received a final argument that is not an array (instead, received `string`). ' +
654+
'When specified, the final argument must be an array.',
655+
'Warning: useCallback received a final argument that is not an array (instead, received `string`). ' +
656+
'When specified, the final argument must be an array.',
657+
]);
658+
expect(() => {
659+
ReactTestRenderer.create(<App deps={100500} />);
660+
}).toWarnDev([
661+
'Warning: useEffect received a final argument that is not an array (instead, received `number`). ' +
662+
'When specified, the final argument must be an array.',
663+
'Warning: useLayoutEffect received a final argument that is not an array (instead, received `number`). ' +
664+
'When specified, the final argument must be an array.',
665+
'Warning: useMemo received a final argument that is not an array (instead, received `number`). ' +
666+
'When specified, the final argument must be an array.',
667+
'Warning: useCallback received a final argument that is not an array (instead, received `number`). ' +
668+
'When specified, the final argument must be an array.',
669+
]);
670+
expect(() => {
671+
ReactTestRenderer.create(<App deps={{}} />);
672+
}).toWarnDev([
673+
'Warning: useEffect received a final argument that is not an array (instead, received `object`). ' +
674+
'When specified, the final argument must be an array.',
675+
'Warning: useLayoutEffect received a final argument that is not an array (instead, received `object`). ' +
676+
'When specified, the final argument must be an array.',
677+
'Warning: useMemo received a final argument that is not an array (instead, received `object`). ' +
678+
'When specified, the final argument must be an array.',
679+
'Warning: useCallback received a final argument that is not an array (instead, received `object`). ' +
680+
'When specified, the final argument must be an array.',
681+
]);
682+
ReactTestRenderer.create(<App deps={[]} />);
683+
ReactTestRenderer.create(<App deps={null} />);
684+
ReactTestRenderer.create(<App deps={undefined} />);
685+
});
686+
687+
it('warns if deps is not an array for useImperativeHandle', () => {
688+
const {useImperativeHandle} = React;
689+
690+
const App = React.forwardRef((props, ref) => {
691+
useImperativeHandle(ref, () => {}, props.deps);
692+
return null;
693+
});
694+
695+
expect(() => {
696+
ReactTestRenderer.create(<App deps={'hello'} />);
697+
}).toWarnDev([
698+
'Warning: useImperativeHandle received a final argument that is not an array (instead, received `string`). ' +
699+
'When specified, the final argument must be an array.',
700+
]);
701+
ReactTestRenderer.create(<App deps={[]} />);
702+
ReactTestRenderer.create(<App deps={null} />);
703+
ReactTestRenderer.create(<App deps={undefined} />);
704+
});
705+
635706
it('assumes useEffect clean-up function is either a function or undefined', () => {
636707
const {useLayoutEffect} = React;
637708

0 commit comments

Comments
 (0)