This warning indicates that component props are being mutated or recreated on every render, breaking React's optimization mechanisms. It typically occurs when using React.memo or hooks with object/array props that have unstable references.
This warning appears when React detects that props passed to a component are changing their reference between renders even though their content might be the same. React relies on reference equality checks for performance optimizations, particularly with React.memo and hooks like useEffect, useMemo, and useCallback. When objects, arrays, or functions are created inline or defined inside a component's render body, they get a new reference on every render. This breaks React's ability to determine if props have actually changed, causing unnecessary re-renders and hook re-executions. This is particularly problematic with React.memo wrapped components, which only do shallow prop comparisons by default. If a parent component passes a newly created object or function as a prop, the memoized child will re-render every time despite having functionally identical props.
Use React DevTools Profiler to identify components that re-render frequently. Look for components wrapped in React.memo or using hooks with object/array dependencies.
// Problem: Object created on every render
function ParentComponent() {
return <MemoizedChild config={{ theme: 'dark', size: 'large' }} />;
}
const MemoizedChild = React.memo(({ config }) => {
return <div>Theme: {config.theme}</div>;
});Check your console for warnings about exhaustive dependencies in useEffect or other hooks.
Wrap object and array props in useMemo to maintain stable references across renders. Only recreate them when their actual values change.
import { useMemo } from 'react';
function ParentComponent() {
// Good: Stable reference unless dependencies change
const config = useMemo(() => ({
theme: 'dark',
size: 'large'
}), []); // Empty array = never changes
return <MemoizedChild config={config} />;
}
// With dynamic values
function ParentWithState({ userPreference }) {
const config = useMemo(() => ({
theme: userPreference,
size: 'large'
}), [userPreference]); // Only recreate when userPreference changes
return <MemoizedChild config={config} />;
}Use useCallback to memoize function props and prevent them from being recreated on every render.
import { useCallback } from 'react';
function ParentComponent() {
const [count, setCount] = useState(0);
// Problem: New function reference on every render
// const handleClick = () => setCount(prev => prev + 1);
// Good: Stable function reference
const handleClick = useCallback(() => {
setCount(prev => prev + 1);
}, []); // No dependencies needed with functional updates
return <MemoizedButton onClick={handleClick} />;
}
// With dependencies
function ParentWithDeps({ userId }) {
const handleSave = useCallback((data) => {
saveToAPI(userId, data);
}, [userId]); // Recreate only when userId changes
return <MemoizedForm onSave={handleSave} />;
}For values that never change, define them outside the component to avoid recreating them on every render.
// Good: Defined once, stable reference
const DEFAULT_CONFIG = {
theme: 'dark',
size: 'large',
timeout: 5000,
};
const VALIDATION_RULES = ['required', 'email', 'minLength'];
function MyComponent() {
return (
<>
<ConfiguredChild config={DEFAULT_CONFIG} />
<ValidatedInput rules={VALIDATION_RULES} />
</>
);
}This is the most efficient solution for truly static data.
Ensure all dependencies in useEffect are properly memoized if they're objects, arrays, or functions.
function MyComponent({ config }) {
// Problem: Runs on every render if config is unstable
// useEffect(() => {
// applyConfig(config);
// }, [config]);
// Solution 1: Memoize the config in parent component
// Solution 2: Destructure only the values you need
useEffect(() => {
const { theme, size } = config;
applyConfig(theme, size);
}, [config.theme, config.size]); // Depend on primitives
// Solution 3: Use a ref for values you don't want to trigger effects
const configRef = useRef(config);
useEffect(() => {
configRef.current = config;
});
useEffect(() => {
// Use configRef.current, no dependency needed
applyConfig(configRef.current);
}, []); // Only run once
}For complex props, provide a custom comparison function to React.memo instead of relying on shallow equality.
const MemoizedComponent = React.memo(
({ config, data }) => {
return <div>{config.theme} - {data.length} items</div>;
},
(prevProps, nextProps) => {
// Return true if props are equal (skip re-render)
// Return false if props are different (re-render)
return (
prevProps.config.theme === nextProps.config.theme &&
prevProps.config.size === nextProps.config.size &&
prevProps.data.length === nextProps.data.length
);
}
);Use this approach when you need deep equality checks or want to compare specific properties.
Performance Profiling: Before optimizing, use React DevTools Profiler to identify actual performance bottlenecks. Premature memoization can make code harder to read without meaningful benefits.
useMemo and useCallback overhead: These hooks have their own cost. Only use them for expensive computations or when passing props to memoized components. For simple components, re-rendering is often faster than memoization overhead.
Deep equality libraries: For complex nested objects, consider libraries like fast-deep-equal or lodash's isEqual in custom comparison functions, but be aware of the performance cost.
State management solutions: If you're frequently passing complex objects through many component layers, consider using Context API with useMemo, or state management libraries like Redux or Zustand that provide stable references.
Immutability patterns: Always create new object/array references when updating state, never mutate existing ones. Use spread operators, Object.assign, or libraries like Immer for complex updates.
ESLint plugin: Install eslint-plugin-react-hooks to catch exhaustive-deps violations automatically during development.
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