useRef Hook

useRef Hook

useRef returns a mutable object { current: initialValue } that persists for the lifetime of the component. Changing ref.current does not trigger a re-render. useRef has two main uses:

Use 1 โ€” Accessing DOM Elements

function AutoFocusInput() {
  const inputRef = useRef(null);

  useEffect(() => {
    // After first render, focus the input
    inputRef.current.focus();
  }, []);

  return <input ref={inputRef} placeholder="Auto-focused on mount" />;
}

// Scrolling to an element
function ScrollToBottom() {
  const bottomRef = useRef(null);

  const scrollDown = () =>
    bottomRef.current.scrollIntoView({ behavior: "smooth" });

  return (
    <div>
      <button onClick={scrollDown}>Scroll to bottom</button>
      {/* ... long content ... */}
      <div ref={bottomRef} />
    </div>
  );
}

Use 2 โ€” Storing Mutable Values Without Re-renders

function Stopwatch() {
  const [elapsed, setElapsed] = useState(0);
  const intervalRef = useRef(null); // Store interval ID โ€” changing it won't re-render

  const start = () => {
    intervalRef.current = setInterval(() => {
      setElapsed(e => e + 1);
    }, 1000);
  };

  const stop  = () => clearInterval(intervalRef.current);
  const reset = () => { stop(); setElapsed(0); };

  // Cleanup on unmount
  useEffect(() => () => clearInterval(intervalRef.current), []);

  return (
    <div>
      <p>{elapsed}s</p>
      <button onClick={start}>Start</button>
      <button onClick={stop}>Stop</button>
      <button onClick={reset}>Reset</button>
    </div>
  );
}

Tracking Previous Value

// Custom hook to track previous value of any variable
function usePrevious(value) {
  const ref = useRef();
  useEffect(() => {
    ref.current = value; // Update AFTER render
  });
  return ref.current; // Returns value from PREVIOUS render
}

function PriceDisplay({ price }) {
  const prevPrice = usePrevious(price);
  const changed   = prevPrice !== undefined && price !== prevPrice;

  return (
    <p style={{ color: changed ? (price > prevPrice ? "green" : "red") : "inherit" }}>
      ${price.toFixed(2)}
      {changed && (price > prevPrice ? " โ–ฒ" : " โ–ผ")}
    </p>
  );
}

๐Ÿง  Test Yourself

Does updating ref.current trigger a re-render?