Leo Cristofani · Web Developer

When to use useRef over useState

Use useRef to persist state between re-renders, but avoid re-renders when the state changes. Use useState to persist state and re-render when the state changes.

More details

Most of the time you’re gonna use useState because you’ll need the component to re-render in order to properly represent the state on the screen.

useRef comes in handy when you need to persist a piece of state that you don’t usually show on the screen. Let's see some examples:

Reference a DOM node

Focus the input when the button is clicked. See it in action

function App() {
  const inputRef = React.useRef(null)

  return (
    <div className="App">
      <h1>useRef - focus input</h1>
      <input ref={inputRef} />
      <button onClick={() => inputRef.current.focus()}>Focus</button>
    </div>
  )
}

Reference a timer

In this "count down" example, we keep a reference to the timer to clean it up when the count down finishes or when the component get's unmounted. See it in action

// ...
function App() {
  const [count, setCount] = React.useState(10)
  const timerRef = React.useRef(null)

  const clearTimer = () => {
    window.clearTimeout(timerRef.current)
  }

  React.useEffect(() => {
    let didCancel = false

    timerRef.current = window.setInterval(() => {
      if (!didCancel) {
        setCount(c => {
          if (c === 0) {
            clearTimer()
            return c
          }
          return c - 1
        })
      }
    }, 1000)

    // ...
  }, [])

  // ...
}

Cache API results

Custom React Hook that fetches data from a given URL and caches the results. See it in action

// ...
const reducer = (state, { type, payload }) => { ... };

const initialState = { ... };

export default function useDogs(url) {
  const [{ data, error, loading }, dispatch] = useReducer(
    reducer,
    initialState
  );

  const cache = useRef({});

  useEffect(() => {
    let didCancel = false;

    if (cache.current[url]) {
      dispatch({ type: FETCH_SUCCESS, payload: cache.current[url] });
      return;
    }

    dispatch({ type: FETCH_REQUEST });
    fetch(url)
      .then(res => res.json())
      .then(res => {
        if (!didCancel) {
          const payload = res.message;
          cache.current[url] = payload;
          dispatch({ type: FETCH_SUCCESS, payload });
        }
      })
      .catch(err => {
        if (!didCancel) {
          dispatch({ type: FETCH_FAILURE, payload: err.message });
        }
      });

    return () => {
      didCancel = true;
    };
  }, [url, cache]);

  return { data, error, loading };
}

Thanks for reading! Have something to say? Please, leave a comment!