Try @eslint-react/kit@beta
logoESLint React

refs

Validates correct usage of refs by checking that 'ref.current' is not read or written during render.

This is an evaluation implementation and may contain false positives or negatives that have not yet been fully audited. Review each report carefully before applying fixes.

Full Name in eslint-plugin-react-x

react-x/refs

Full Name in @eslint-react/eslint-plugin

@eslint-react/refs

Features

🧪

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() or React.createRef().

    const scrollRef = useRef(null);
  • An identifier named ref or ending in Ref that reads from or writes to .current.

    buttonRef.current = node;
  • Passed through a JSX ref prop (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).

Examples

Reading ref.current during render

Refs are not guaranteed to be initialized during render. Reading them in the component body may return null or a stale value from a previous render cycle.

// Problem: reading ref during render
function Component() {
  const ref = useRef(0);
  const value = ref.current; // read during render
  //            ^^^ 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>;
}
// Recommended: use state to display values
function Component() {
  const [count, setCount] = useState(0);
  return <div>{count}</div>;
}

Reading ref.current in custom hooks

Custom hooks follow the same rules as components. Reading ref.current during the execution of a custom hook is still part of the render phase.

// Problem: reading ref.current inside a custom hook during render
function useMyHook() {
  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 value;
}
// Recommended: pass the ref to a component and read it in an effect
function Component() {
  const ref = useRef(null);
  useEffect(() => {
    if (ref.current) {
      console.log(ref.current.offsetWidth);
    }
  }, []);
  return <div ref={ref} />;
}

Modifying ref.current during render

Writing to a ref during render is a side effect. React may invoke your component multiple times before committing, so the ref write may happen more often than you expect.

// Problem: modifying ref during render
function Component({ value }) {
  const ref = useRef(null);
  ref.current = value; // modified during render
  // ^^^ 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 />;
}
// Recommended: modify refs in effects or event handlers
function Component() {
  const ref = useRef(null);
  useEffect(() => {
    if (ref.current) {
      console.log(ref.current.offsetWidth);
    }
  });
  return <div ref={ref} />;
}
// Recommended: use useLayoutEffect when you need to measure DOM nodes before paint
function Component() {
  const ref = useRef(null);
  useLayoutEffect(() => {
    console.log(ref.current);
  }, []);
  return <div ref={ref} />;
}

Lazy initialization of ref value

There is one exception: lazy initialization of a ref value on first access is a common and safe pattern, as long as it only runs once.

// OK: lazy initialization of ref value
function Component() {
  const ref = useRef(null);
  if (ref.current === null) {
    ref.current = expensiveComputation(); // lazy initialization
  }
  const handleClick = () => {
    console.log(ref.current);
  };
  return <button onClick={handleClick}>Click</button>;
}

Using refs for rendered values

If a value affects what is shown on screen, it should be state, not a ref. Refs are meant for values that don't affect rendering output.

// Problem: using a ref to store a value that needs to be displayed in JSX
function Component() {
  const ref = useRef(0);
  return <div>{ref.current}</div>;
  //          ^^^ Do not read 'ref.current' during render.
}
// Recommended: use state to store rendered values
function Component() {
  const [count, setCount] = useState(0);
  return <div>{count}</div>;
}

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.

Versions

Resources

Further Reading


See Also

On this page