Back to all notes

Latest Ref Pattern : Access the Latest Value Without Re-running Effects

Keep event handlers up to date using a ref instead of effect dependencies.

2 min read

Introduction

The latest ref pattern stores the latest version of a value (often a function) in a ref so you can use it inside long‑lived handlers without re‑subscribing effects.

Why this matters

Event listeners and subscriptions often need access to the latest callback. Adding functions to effect deps causes the effect to re‑subscribe on every render. A ref keeps the latest function without re‑running the effect.

The problem

If you capture a callback in an effect without keeping it in sync, the listener uses a stale closure. If you add the callback to the dependencies, the effect re-subscribes every render.

Inefficient approach

Stale closure or constant re-subscription:

import { useEffect } from "react";

function ClickHandler({ onClick }) {
  useEffect(() => {
    const handleClick = () => onClick(); // stale if onClick changes
    document.addEventListener("click", handleClick);
    return () => document.removeEventListener("click", handleClick);
  }, [onClick]); // avoids staleness but re-subscribes every render

  return <p>Click anywhere on the page</p>;
}

function App() {
  const handleClick = () => {
    console.log("Clicked!");
  };

  return (
    <div>
      <h1>Latest Ref Pattern</h1>
      <ClickHandler onClick={handleClick} />
    </div>
  );
}

export default App;

The solution

Store the latest onClick in a ref, update it every render, and use it inside a once‑registered listener.

import { useRef, useEffect } from "react";

function ClickHandler({ onClick }) {
  const onClickRef = useRef(onClick);

  useEffect(() => {
    onClickRef.current = onClick;
  });

  useEffect(() => {
    const handleClick = () => {
      onClickRef.current();
    };
    document.addEventListener("click", handleClick);
    return () => document.removeEventListener("click", handleClick);
  }, []);

  return <p>Click anywhere on the page</p>;
}

function App() {
  const handleClick = () => {
    console.log("Clicked!");
  };

  return (
    <div>
      <h1>Latest Ref Pattern</h1>
      <ClickHandler onClick={handleClick} />
    </div>
  );
}

export default App;

Step-by-step

  1. Create const onClickRef = useRef(onClick).
  2. In a no-deps effect, update onClickRef.current = onClick every render.
  3. Register the DOM listener once; call onClickRef.current() inside.
  4. Clean up the listener on unmount.

Tips

  • Initialize the ref with the first function value for clarity.
  • Keep the “update ref” effect free of deps to run every render.
  • This pattern generalizes to any subscription that needs fresh callbacks.

Knowledge base

Problem snippet:

document.addEventListener("click", () => onClick());

Solution snippet:

const onClickRef = useRef(onClick);
onClickRef.current = onClick;
document.addEventListener("click", () => onClickRef.current());