refs
Validates correct usage of refs by checking that 'ref.current' is not read or written during render.
This rule is currently in rc and only available in v3.0.0 rc releases.
This rule is experimental and may change in the future or be removed. It is not recommended for use in production code at this time.
Full Name in eslint-plugin-react-x@rc
react-x/refsFull Name in @eslint-react/eslint-plugin@rc
@eslint-react/refsFeatures
🧪
Presets
Rule Details
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>;
}Invalid
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;
}Valid
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} />;
}Resources
Further Reading
See Also
react-naming-convention/ref-name
Enforces identifier names assigned fromuseRefcalls to be eitherrefor end withRef.react-x/no-create-ref
DisallowscreateRefin function components.react-x/no-forward-ref
Replaces usage offorwardRefwith passingrefas a prop.