useRef: The Escape Hatch

The useRef Hook serves two primary purposes:

1. Accessing DOM Elements: This is its most common use case. It provides a way to get a direct reference to a DOM element rendered by your component. This is useful for managing focus, triggering animations, or integrating with third-party DOM libraries.

JavaScript


import React, { useRef, useEffect } from 'react';

function TextInputWithFocus() {
  // Create a ref object
  const inputRef = useRef(null);

  // Use useEffect to focus the input when the component mounts
  useEffect(() => {
    // The actual DOM node is available on the .current property
    inputRef.current.focus();
  }, []); // Empty dependency array means this runs only once

  return <input ref={inputRef} type="text" />;
}

2. Storing a Mutable Value that Doesn't Cause a Re-render: Unlike useState, updating a ref's .current property does not trigger a re-render. This makes useRef perfect for storing information that needs to persist across renders but doesn't directly affect the visual output, like a timer ID or a previous state value.

JavaScript


const timerIdRef = useRef(null);

const startTimer = () => {
  timerIdRef.current = setInterval(() => { ... });
};

const stopTimer = () => {
  clearInterval(timerIdRef.current);
};
Performance Hooks: useMemo and useCallback

These hooks are tools for optimization. You should not use them everywhere, only when you have a measurable performance problem.

useMemo: Memoizing Values The useMemo Hook is for memoizing the result of an expensive calculation. It takes a function and a dependency array. React will only re-run the function (and re-calculate the value) if one of the dependencies has changed.

Imagine a function that filters a huge list, which is a slow operation:

JavaScript


import React, { useState, useMemo } from 'react';

function TodoList({ todos, filter }) {
  // This expensive calculation runs on every single render
  // const visibleTodos = filterTodos(todos, filter);

  // With useMemo, filterTodos will only be called again
  // if 'todos' or 'filter' changes.
  const visibleTodos = useMemo(() => {
    console.log("Running expensive filtering...");
    return filterTodos(todos, filter);
  }, [todos, filter]);

  return (
    <ul>
      {visibleTodos.map(todo => <li key={todo.id}>{todo.text}</li>)}
    </ul>
  );
}

If the component re-renders for another reason (e.g., a parent's state changes), useMemo will return the previously calculated visibleTodos array without running the expensive filtering logic again.

useCallback: Memoizing Functions The useCallback Hook is very similar, but instead of memoizing a value, it memoizes a function. This is useful when you are passing callbacks to child components that are optimized to only re-render when their props change.

JavaScript


import React, { useState, useCallback } from 'react';
import MyButton from './MyButton'; // Assume MyButton is a memoized component

function ParentComponent() {
  const [count, setCount] = useState(0);

  // Without useCallback, a new handleClick function is created on every render.
  // This would cause MyButton to re-render even if it's memoized.
  // const handleClick = () => {
  //   console.log("Button clicked!");
  // };

  // With useCallback, the same function instance is returned as long
  // as its dependencies (none, in this case) don't change.
  const handleClick = useCallback(() => {
    console.log("Button clicked!");
  }, []); // Empty dependency array

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(c => c + 1)}>Increment</button>
      <MyButton onClick={handleClick} />
    </div>
  );
}

By wrapping handleClick in useCallback, we ensure that the MyButton component receives the exact same function prop on every render, preventing unnecessary re-renders.