This error occurs when attempting to pass a callback function as a second argument to the useState setter, similar to class component setState. Unlike class components, useState setters only accept the new state value and do not support callback arguments.
This error appears when developers try to use the useState setter function with a second callback argument, mimicking the behavior of class component setState. In class components, setState accepts an optional callback function as a second parameter that executes after the state update completes. However, the useState hook in functional components was designed differently - the setter function returned by useState only accepts a single argument: the new state value or an updater function. The React team made a conscious design decision not to include callback support in useState. This was intentional, not an oversight. The rationale was that the useEffect and useLayoutEffect hooks provide a cleaner, more declarative way to handle side effects after state changes. This design encourages better separation of concerns between state updates and side effects. When you pass a second argument to a useState setter, React throws this error because the function signature doesn't support it. The error typically surfaces during development when migrating code from class components to functional components, or when developers unfamiliar with hooks attempt to replicate class component patterns.
Search your codebase for patterns where useState setters are called with two arguments. Look for code like this:
// INCORRECT - This causes the error
setState(newValue, () => {
console.log('State updated');
});Use your IDE's search functionality or grep to find instances of , after setState calls. Check all event handlers, async functions, and effect hooks where state is being updated.
The recommended approach is to use useEffect with a dependency array containing the state variable. This effect will run whenever the state changes:
import { useState, useEffect } from 'react';
function MyComponent() {
const [age, setAge] = useState(0);
// This runs after age updates
useEffect(() => {
console.log('Age updated to:', age);
// Your callback logic here
}, [age]);
const handleClick = () => {
setAge(21); // No second argument
};
return <button onClick={handleClick}>Update Age</button>;
}The dependency array [age] tells React to run the effect only when age changes. This provides the same functionality as the class component callback.
If your callback should only run under specific conditions (not on initial render), use a ref to track whether it's the first render:
import { useState, useEffect, useRef } from 'react';
function MyComponent() {
const [count, setCount] = useState(0);
const isFirstRender = useRef(true);
useEffect(() => {
if (isFirstRender.current) {
isFirstRender.current = false;
return; // Skip effect on first render
}
// This only runs after subsequent updates
console.log('Count changed:', count);
}, [count]);
return <button onClick={() => setCount(count + 1)}>Increment</button>;
}This pattern prevents the effect from running on component mount, only triggering after state updates.
If you need to access the previous state value while updating, use the functional form of setState instead of a callback:
// INCORRECT - Trying to access previous value with callback
setState(newValue, (prevValue) => {
console.log('Previous:', prevValue);
});
// CORRECT - Use functional update
setState(prevValue => {
console.log('Previous:', prevValue);
return prevValue + 1;
});The functional form receives the current state as an argument and returns the new state. This is especially important when state updates depend on the previous value.
If you need useState-with-callback functionality throughout your app, create a custom hook:
import { useState, useEffect, useRef } from 'react';
function useStateCallback(initialState) {
const [state, setState] = useState(initialState);
const callbackRef = useRef(null);
const setStateWithCallback = (newState, callback) => {
callbackRef.current = callback;
setState(newState);
};
useEffect(() => {
if (callbackRef.current) {
callbackRef.current(state);
callbackRef.current = null;
}
}, [state]);
return [state, setStateWithCallback];
}
// Usage
const [age, setAge] = useStateCallback(0);
setAge(21, (updatedAge) => {
console.log('Age is now:', updatedAge);
});This provides a class-component-like API while maintaining hooks compatibility. Use this sparingly - direct useEffect is generally clearer.
If you're using TypeScript and implementing custom callback solutions, ensure proper typing:
import { useState, useEffect, useRef, Dispatch, SetStateAction } from 'react';
type Callback<T> = (value: T) => void;
type SetStateWithCallback<T> = (
newState: SetStateAction<T>,
callback?: Callback<T>
) => void;
function useStateCallback<T>(
initialState: T
): [T, SetStateWithCallback<T>] {
const [state, setState] = useState<T>(initialState);
const callbackRef = useRef<Callback<T> | null>(null);
const setStateWithCallback: SetStateWithCallback<T> = (newState, callback) => {
callbackRef.current = callback || null;
setState(newState);
};
useEffect(() => {
if (callbackRef.current) {
callbackRef.current(state);
callbackRef.current = null;
}
}, [state]);
return [state, setStateWithCallback];
}This provides full type safety for your custom hook implementation.
Verify that your refactored code handles the asynchronous nature of state updates correctly:
function MyComponent() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log('Effect runs with count:', count);
}, [count]);
const handleClick = () => {
console.log('Before update:', count);
setCount(count + 1);
console.log('After setState (still old value):', count);
// The effect will run after this function completes
};
return <button onClick={handleClick}>Count: {count}</button>;
}Remember that setState is asynchronous - you cannot access the new value immediately after calling it. The useEffect runs after the component re-renders with the new state.
useLayoutEffect vs useEffect for callbacks: If your callback needs to run synchronously before the browser paints (similar to componentDidUpdate in class components), use useLayoutEffect instead of useEffect. This is important for DOM measurements or preventing visual flicker, but comes with a performance cost since it blocks the browser paint.
Multiple state updates in one render: If you need to update multiple state values and run a callback only after all have updated, combine them into a single state object or use useReducer. Each individual setState call can trigger a separate re-render, and useEffect will run for each change unless you batch them.
Batching behavior differences: React 18 introduced automatic batching for all state updates, including those in promises, setTimeout, and native event handlers. In React 17 and earlier, only updates in React event handlers were batched. This affects when your useEffect callbacks run - multiple setState calls might trigger the effect only once in React 18.
Custom hook libraries: Several community packages provide useState-with-callback functionality, including "use-state-with-callback" and "use-state-callback". Evaluate whether the added dependency is worth the convenience, as the useEffect pattern is generally preferred and more idiomatic.
Debugging infinite loops: When migrating callback logic to useEffect, ensure your dependency array is correct. Missing or incorrect dependencies can cause infinite render loops. If the effect updates state that's in its own dependency array, you'll create an infinite loop. Use React DevTools Profiler to detect this.
Class component migration checklist: When converting from class components, map this.setState's second argument to useEffect with appropriate dependencies, ensure componentDidMount logic goes in useEffect with empty array [], and componentDidUpdate conditional logic becomes useEffect with specific dependencies. The patterns are different but equivalent in functionality.
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