Chapter 4 of 4

Refs in Practice

Two patterns worth keeping, and the mistakes to avoid.

Remembering the previous value

The effect runs after render, so during render the ref still holds the old value.
function usePrevious(value) {
  const ref = useRef(undefined);

  useEffect(() => {
    ref.current = value;
  }, [value]);

  return ref.current;   // the value from the previous render
}

function Price({ amount }) {
  const previous = usePrevious(amount);
  const hasRisen = previous !== undefined && amount > previous;

  return <span className={hasRisen ? "up" : ""}>{amount}</span>;
}

Keeping a callback fresh without re-subscribing

When an effect should not re-run but needs the latest version of a callback, park the callback in a ref.

function useInterval(callback, delay) {
  const savedCallback = useRef(callback);

  useEffect(() => {
    savedCallback.current = callback;
  }, [callback]);

  useEffect(() => {
    const id = setInterval(() => savedCallback.current(), delay);
    return () => clearInterval(id);
  }, [delay]);   // the interval only resets when the delay changes
}

Mistakes to avoid

  • Reading ref.current during render. It may be null, and reading it makes the render impure.
  • Writing to a ref during render for the same reason - do it in an effect or a handler.
  • Using a ref for something the UI displays. That is state.
  • Using a ref to avoid a dependency warning. Fix the dependency instead.