diff --git a/debug/src/debug.js b/debug/src/debug.js
index 749288053d..58a6e40ef0 100644
--- a/debug/src/debug.js
+++ b/debug/src/debug.js
@@ -357,6 +357,27 @@ export function initDebug() {
keys.push(key);
}
}
+
+ if (vnode._component != null && vnode._component.__hooks != null) {
+ // Validate that none of the hooks in this component contain arguments that are NaN.
+ // This is a common mistake that can be hard to debug, so we want to catch it early.
+ const hooks = vnode._component.__hooks._list;
+ if (hooks) {
+ for (let i = 0; i < hooks.length; i += 1) {
+ const hook = hooks[i];
+ if (hook._args) {
+ for (const arg of hook._args) {
+ if (Number.isNaN(arg)) {
+ const componentName = getDisplayName(vnode);
+ throw new Error(
+ `Invalid argument passed to hook. Hooks should not be called with NaN in the dependency array. Hook index ${i} in component ${componentName} was called with NaN.`
+ );
+ }
+ }
+ }
+ }
+ }
+ }
};
}
diff --git a/debug/test/browser/validateHookArgs.test.js b/debug/test/browser/validateHookArgs.test.js
new file mode 100644
index 0000000000..19f7cb7fd3
--- /dev/null
+++ b/debug/test/browser/validateHookArgs.test.js
@@ -0,0 +1,74 @@
+import { createElement, render, createRef } from 'preact';
+import {
+ useState,
+ useEffect,
+ useLayoutEffect,
+ useCallback,
+ useMemo,
+ useImperativeHandle
+} from 'preact/hooks';
+import { setupRerender } from 'preact/test-utils';
+import { setupScratch, teardown } from '../../../test/_util/helpers';
+import 'preact/debug';
+
+/** @jsx createElement */
+
+describe('Hook argument validation', () => {
+ /**
+ * @param {string} name
+ * @param {(arg: number) => void} hook
+ */
+ function validateHook(name, hook) {
+ const TestComponent = ({ initialValue }) => {
+ const [value, setValue] = useState(initialValue);
+ hook(value);
+
+ return (
+
+ );
+ };
+
+ it(`should error if ${name} is mounted with NaN as an argument`, async () => {
+ expect(() =>
+ render(, scratch)
+ ).to.throw(/Hooks should not be called with NaN in the dependency array/);
+ });
+
+ it(`should error if ${name} is updated with NaN as an argument`, async () => {
+ render(, scratch);
+
+ expect(() => {
+ scratch.querySelector('button').click();
+ rerender();
+ }).to.throw(
+ /Hooks should not be called with NaN in the dependency array/
+ );
+ });
+ }
+
+ /** @type {HTMLElement} */
+ let scratch;
+ /** @type {() => void} */
+ let rerender;
+
+ beforeEach(() => {
+ scratch = setupScratch();
+ rerender = setupRerender();
+ });
+
+ afterEach(() => {
+ teardown(scratch);
+ });
+
+ validateHook('useEffect', arg => useEffect(() => {}, [arg]));
+ validateHook('useLayoutEffect', arg => useLayoutEffect(() => {}, [arg]));
+ validateHook('useCallback', arg => useCallback(() => {}, [arg]));
+ validateHook('useMemo', arg => useMemo(() => {}, [arg]));
+
+ const ref = createRef();
+ validateHook('useImperativeHandle', arg => {
+ useImperativeHandle(ref, () => undefined, [arg]);
+ });
+});