This error occurs when useCallback receives a non-array value as its second argument instead of a dependency array. The dependencies parameter must always be an array, even if empty.
The "useCallback received a final argument of type X instead of an array" error in React occurs when the useCallback hook receives an invalid type for its second argument. The useCallback hook expects exactly two parameters: a callback function and a dependencies array. When you pass a non-array value (like an object, boolean, number, or string) as the second argument, React throws this error. The dependencies array is critical to how useCallback works. React uses this array to determine when to return a new function reference versus the memoized version from the previous render. The array should contain all reactive values (props, state, variables) that are referenced inside the callback function. If the dependency array is omitted or replaced with a non-array value, useCallback cannot perform its memoization correctly. This error typically happens due to simple syntax mistakes, such as accidentally passing an object instead of wrapping values in an array, or confusing useCallback's signature with other hooks that might accept different parameter types.
Check your error stack trace to find the exact component and line where the error occurs. The error message will typically point to the specific useCallback invocation that's receiving an invalid argument.
Look for patterns like:
// ❌ Wrong - passing an object
const callback = useCallback(() => {
// function code
}, { dependency: value });
// ❌ Wrong - passing a boolean
const callback = useCallback(() => {
// function code
}, true);
// ❌ Wrong - missing brackets
const callback = useCallback(() => {
// function code
}, dependency);Ensure the second parameter to useCallback is always an array, even if it's empty:
// ✅ Correct - empty array
const callback = useCallback(() => {
// function code
}, []);
// ✅ Correct - array with dependencies
const callback = useCallback(() => {
console.log(value);
}, [value]);
// ✅ Correct - multiple dependencies
const callback = useCallback(() => {
doSomething(prop1, prop2);
}, [prop1, prop2]);If your callback doesn't depend on any reactive values, use an empty array [].
Review the callback function body and list all reactive values it references. Include:
- Props used inside the callback
- State variables accessed in the callback
- Context values
- Variables declared in the component body
- Other functions defined in the component
function MyComponent({ userId, onComplete }) {
const [count, setCount] = useState(0);
const apiUrl = '/api/users';
// All reactive values must be in the dependency array
const handleSubmit = useCallback(() => {
fetch(`${apiUrl}/${userId}`)
.then(() => {
setCount(c => c + 1); // setCount is stable, doesn't need to be included
onComplete();
});
}, [userId, onComplete, apiUrl]); // Include all referenced values
return <button onClick={handleSubmit}>Submit</button>;
}Install and configure the React hooks ESLint plugin to catch dependency errors automatically:
npm install eslint-plugin-react-hooks --save-devAdd to your .eslintrc.js:
module.exports = {
plugins: ['react-hooks'],
rules: {
'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': 'warn'
}
};The exhaustive-deps rule will warn you when dependencies are missing or specified incorrectly, preventing this type of error.
After fixing the dependency array, verify that:
1. The component renders without errors
2. The callback executes correctly when invoked
3. The callback updates when dependencies change
4. No unnecessary re-renders occur
import { useCallback, useState } from 'react';
function TestComponent() {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
console.log('Current count:', count);
setCount(count + 1);
}, [count]); // Callback will update when count changes
return (
<div>
<p>Count: {count}</p>
<button onClick={handleClick}>Increment</button>
</div>
);
}Test edge cases like rapid clicks, prop changes, and unmounting to ensure stability.
Evaluate whether memoization is actually needed. useCallback has overhead and should only be used when:
1. Passing callbacks to optimized child components that use React.memo:
const MemoizedChild = React.memo(ChildComponent);
function Parent() {
const handleClick = useCallback(() => {
// Without useCallback, MemoizedChild re-renders unnecessarily
}, []);
return <MemoizedChild onClick={handleClick} />;
}2. Callback is a dependency of other hooks:
const fetchData = useCallback(() => {
return fetch('/api/data');
}, []);
useEffect(() => {
fetchData(); // Stable reference prevents effect from running repeatedly
}, [fetchData]);If neither applies, a regular function may be simpler and more performant.
TypeScript Integration: When using TypeScript, the type signature of useCallback enforces the array requirement at compile time. If you see this runtime error in a TypeScript project, it suggests the type checking may be bypassed (using 'any' types) or the error occurs in JavaScript files.
Performance Considerations: While useCallback prevents function recreation, it doesn't prevent the function from being called. If your callback contains expensive computations, consider using useMemo to cache the computation results instead of just the function reference.
Dependency Array Gotchas: Object and array dependencies are compared by reference, not value. If you pass an object literal or array literal as a dependency, it will be considered different on every render:
// ❌ Wrong - object literal creates new reference each render
const callback = useCallback(() => {
console.log(config);
}, [{ setting: true }]); // Always creates new callback
// ✅ Correct - stable reference
const config = useMemo(() => ({ setting: true }), []);
const callback = useCallback(() => {
console.log(config);
}, [config]); // Only updates when config reference changesReact 19 and Beyond: Future versions of React may introduce automatic dependency inference through the React Compiler, which could eliminate the need for manual dependency arrays. The useEffectEvent proposal (experimental) may also provide an alternative for callbacks that don't need dependency tracking.
Common Mistake with Conditional Logic: Don't conditionally create useCallback hooks. All hooks must be called in the same order on every render:
// ❌ Wrong - conditional hook
if (condition) {
const callback = useCallback(() => {}, []);
}
// ✅ Correct - hook always called, logic inside is conditional
const callback = useCallback(() => {
if (condition) {
// conditional logic here
}
}, [condition]);React Hook useCallback has a missing dependency: 'variable'. Either include it or remove the dependency array react-hooks/exhaustive-deps
React Hook useCallback has a missing dependency
Cannot use private fields in class components without TS support
Cannot use private fields in class components without TS support
Cannot destructure property 'xxx' of 'undefined'
Cannot destructure property of undefined when accessing props
useNavigate() may be used only in the context of a <Router> component.
useNavigate() may be used only in the context of a Router component
Cannot find module or its corresponding type declarations
How to fix "Cannot find module or type declarations" in Vite