This ESLint warning occurs when a variable or function used inside useRef is not listed in its dependency array. The react-hooks/exhaustive-deps rule enforces complete dependency tracking to prevent stale closures and ensure refs update when their dependencies change.
This warning is triggered by the ESLint plugin react-hooks/exhaustive-deps rule when it detects that a ref created with useRef references variables, props, or state that are not included in the dependency array. The rule exists to prevent common bugs where refs capture stale values from previous renders. Unlike useCallback and useMemo, useRef doesn't typically have a dependency array in its basic usage. However, when you use useRef to create a ref that depends on other values (like creating a ref with an initial value that depends on props), or when you use useRef inside a custom hook that follows the dependencies pattern, ESLint may warn about missing dependencies. The exhaustive-deps rule analyzes your ref usage and compares all referenced values against any dependency arrays in the surrounding hook. It warns you about any missing dependencies so you can decide whether to add them, restructure your code, or (rarely) explicitly acknowledge that you want the stale behavior.
First, determine if your useRef usage genuinely needs dependency tracking. Basic useRef for DOM references or mutable values typically doesn't:
// No dependencies needed - basic DOM ref
const inputRef = useRef<HTMLInputElement>(null);
// No dependencies needed - mutable value
const timeoutId = useRef<NodeJS.Timeout | null>(null);
// Dependencies MAY be needed - ref depends on prop
const computedRef = useRef(expensiveComputation(props.data)); // Warning if props.data not in depsIf your ref's initial value depends on props or state, you might need to handle updates separately.
For refs that need to stay synchronized with props or state, update them in useEffect rather than trying to recreate the ref:
// Before: Ref initialization depends on prop
const dataRef = useRef(processData(props.items)); // Warning: missing props.items
// After: Initialize empty, update in useEffect
const dataRef = useRef<ProcessedData | null>(null);
useEffect(() => {
dataRef.current = processData(props.items);
}, [props.items]); // Proper dependency tracking in useEffectThis pattern separates ref creation (which happens once) from value updates (which happen when dependencies change).
If you need a computed value that updates with dependencies, consider useMemo instead of useRef:
// Before: Trying to compute ref value with dependencies
const computedValue = useRef(expensiveCalculation(props.a, props.b));
// Warning: missing props.a, props.b
// After: Use useMemo for computed values
const computedValue = useMemo(
() => expensiveCalculation(props.a, props.b),
[props.a, props.b]
);
// If you still need a ref to this value (for mutation):
const computedRef = useRef(computedValue);
useEffect(() => {
computedRef.current = computedValue;
}, [computedValue]);useMemo is designed for computed values with dependencies, while useRef is for mutable references.
When creating custom hooks that use useRef and accept dependencies, ensure proper dependency tracking:
// Custom hook with useRef and dependencies
function usePrevious<T>(value: T): T | undefined {
const ref = useRef<T>();
useEffect(() => {
ref.current = value;
}, [value]); // Dependency properly tracked in useEffect
return ref.current;
}
// Usage - no warnings
const previousCount = usePrevious(count);The key is to use useEffect inside the custom hook to update the ref when dependencies change, not to make the ref itself depend on them.
Remember that useRef is designed for stable references across renders, not for computed values. If you find yourself wanting to recreate a ref when dependencies change, reconsider your approach:
// Wrong pattern: Recreating ref on dependency change
const [items, setItems] = useState([]);
const itemsRef = useRef(items); // Warning, but also wrong pattern
// Better: Store current value separately
const [items, setItems] = useState([]);
const itemsRef = useRef();
useEffect(() => {
itemsRef.current = items;
}, [items]);
// Or even better: Do you need a ref at all?
// Often, state or a derived value is sufficientAsk: "Do I need a mutable reference that persists across renders, or just the current value?"
If you've determined the warning is a false positive (rare with useRef), disable it with a comment:
// This ref is for a DOM element, not dependent on props
const inputRef = useRef<HTMLInputElement>(null); // No warning usually
// If you get a false warning in a complex case:
const stableRef = useRef({
// Complex initialization that ESLint misinterprets
initialize() { /* ... */ }
// eslint-disable-next-line react-hooks/exhaustive-deps
}); // Intentionally empty despite complex initWarning: Only disable the rule if you're certain. Most useRef dependency warnings indicate real issues with value synchronization.
Understanding useRef's purpose: useRef creates a mutable reference that persists across renders. Unlike state, updating a ref doesn't trigger re-renders. This makes it ideal for: (1) DOM element references, (2) storing mutable values that shouldn't trigger renders, (3) keeping previous values, (4) storing timeout/interval IDs, (5) imperative animations.
Why useRef rarely needs dependencies: The ref object itself (the .current property container) is stable across renders. Only the .current value changes. This is why you typically don't need dependencies for useRef - you're not recreating the ref container, just updating its contents.
Custom hooks and dependency arrays: When writing custom hooks that accept dependency arrays (like useMemoizedValue or useDebouncedCallback), you must follow the same dependency rules as built-in hooks. ESLint's exhaustive-deps rule can be configured to check custom hooks via the additionalHooks option.
useRef vs useMemo vs useState:
- useRef: Mutable reference, no re-renders on update, persists across renders
- useMemo: Computed value, re-computes when dependencies change, for expensive calculations
- useState: Immutable state, triggers re-renders on update, for data that affects UI
Common false positives: ESLint sometimes warns about useRef in these cases: (1) Complex initializers with function calls, (2) Custom hooks that ESLint doesn't recognize, (3) Ref callbacks that reference outer variables. In these cases, verify if it's truly a false positive or if your code needs restructuring.
Performance considerations: useRef is very lightweight - it's just creating an object with a current property. Don't avoid useRef for performance reasons. The warning is about correctness, not performance.
React Strict Mode: In Strict Mode, React double-invokes components in development. This can reveal bugs with useRef if you're relying on ref initialization happening exactly once. Ensure your ref logic is idempotent or use useEffect for side effects.
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