refs
Full Name in @eslint-react/eslint-plugin@beta
@eslint-react/refsFull Name in eslint-plugin-react-x@beta
react-x/refsPresets
Description
Validates correct usage of refs by checking that ref.current is not read or written during render.
Refs hold values that aren't used for rendering. Unlike state, changing a ref doesn't trigger a re-render. Reading or writing ref.current during render breaks React's expectations. Refs might not be initialized when you try to read them, and their values can be stale or inconsistent.
How It Detects Refs
The lint only applies these rules to values it knows are refs. A value is inferred as a ref when:
-
Returned from
useRef()orReact.createRef().const scrollRef = useRef(null); -
An identifier named
refor ending inRefthat reads from or writes to.current.buttonRef.current = node; -
Passed through a JSX
refprop (for example<div ref={someRef} />).<input ref={inputRef} />
Once something is marked as a ref, the lint surfaces violations when ref.current is accessed directly in the render phase of a component or hook body (i.e. not inside effects, event handlers, callbacks, or other nested functions).
Common Violations
- Reading
ref.currentduring render - Updating
refsduring render - Using
refsfor values that should be state
Invalid
// ❌ Reading ref during render
function Component() {
const ref = useRef(0);
const value = ref.current; // Don't read during render
return <div>{value}</div>;
}// ❌ Modifying ref during render
function Component({ value }) {
const ref = useRef(null);
ref.current = value; // Don't modify during render
return <div />;
}Valid
// ✅ Read ref in effects/handlers
function Component() {
const ref = useRef(null);
useEffect(() => {
if (ref.current) {
console.log(ref.current.offsetWidth); // OK in effect
}
});
return <div ref={ref} />;
}// ✅ Use state for UI values
function Component() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
{count}
</button>
);
}// ✅ Lazy initialization of ref value
function Component() {
const ref = useRef(null);
// Initialize only once on first use
if (ref.current === null) {
ref.current = expensiveComputation(); // OK - lazy initialization
}
const handleClick = () => {
console.log(ref.current); // Use the initialized value
};
return <button onClick={handleClick}>Click</button>;
}Troubleshooting
The lint flagged my plain object with .current
The name heuristic intentionally treats ref.current and fooRef.current as real refs. If you're modeling a custom container object, pick a different name (for example, box) or move the mutable value into state. Renaming avoids the lint because the compiler stops inferring it as a ref.
I need to read a ref value in JSX
Refs are meant for values that don't affect rendering output. If you need to display a value in JSX, use state instead:
// ❌ Wrong: Reading ref in JSX
function Component() {
const ref = useRef(0);
return <div>{ref.current}</div>;
}// ✅ Correct: Use state for rendered values
function Component() {
const [count, setCount] = useState(0);
return <div>{count}</div>;
}Examples
Failing
function Component() {
const ref = useRef(0);
const value = ref.current;
// ^^^^^^^^^^^
// - Do not read 'ref.current' during render. Refs are not available during rendering
// and their values may be stale or inconsistent. Move this read into an effect or
// event handler.
return <div>{value}</div>;
}function Component({ value }) {
const ref = useRef(null);
ref.current = value;
// ^^^^^^^^^^^
// - Do not write to 'ref.current' during render. Refs should only be mutated in effects
// or event handlers. Move this write into an effect or event handler.
return <div />;
}function Component() {
const ref = useRef(0);
return <div>{ref.current}</div>;
// ^^^^^^^^^^^
// - Do not read 'ref.current' during render.
}function useMyHook() {
const ref = useRef(0);
const value = ref.current;
// ^^^^^^^^^^^
// - Do not read 'ref.current' during render.
return value;
}Passing
function Component() {
const ref = useRef(null);
useEffect(() => {
if (ref.current) {
console.log(ref.current.offsetWidth);
}
});
return <div ref={ref} />;
}function Component() {
const ref = useRef(null);
const handleClick = () => {
console.log(ref.current);
};
return <button onClick={handleClick}>Click</button>;
}function Component() {
const ref = useRef(null);
if (ref.current === null) {
ref.current = expensiveComputation();
}
return <div />;
}function Component() {
const ref = useRef(null);
useLayoutEffect(() => {
console.log(ref.current);
}, []);
return <div ref={ref} />;
}