devnotes.

useRef, useContext & useReducer

useRef — a mutable box that survives re-renders

useRef returns an object { current: value } that persists across renders but, unlike state, updating it does not trigger a re-render. Two main uses:

1. Accessing a DOM node directly

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

  useEffect(() => { inputRef.current.focus(); }, []);

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

2. Storing a mutable value that shouldn't cause a re-render

function Stopwatch() {
  const [seconds, setSeconds] = useState(0);
  const intervalRef = useRef(null);

  function start() {
    intervalRef.current = setInterval(() => setSeconds(s => s + 1), 1000);
  }
  function stop() {
    clearInterval(intervalRef.current);
  }

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

useContext — avoiding prop drilling

Prop drilling is passing a prop through many layers of components that don't need it, just so a deeply nested child can use it. Context lets you skip the middle layers.

const ThemeContext = createContext('light');

function App() {
  return (
    <ThemeContext.Provider value="dark">
      <Toolbar />
    </ThemeContext.Provider>
  );
}

function Toolbar() {
  return <ThemedButton />; // doesn't need to know about theme at all
}

function ThemedButton() {
  const theme = useContext(ThemeContext);
  return <button className={`btn-${theme}`}>Save</button>;
}

Context is best for genuinely global/cross-cutting data: theme, authenticated user, locale. For frequently changing data or complex app state, a dedicated state manager (Zustand, Redux Toolkit, Jotai) usually scales better than Context alone.

useReducer — state logic as a reducer function

When state transitions are complex (multiple sub-values, or the next state depends on the action taken), useReducer keeps update logic in one predictable place, similar to Redux:

function reducer(state, action) {
  switch (action.type) {
    case 'increment': return { count: state.count + 1 };
    case 'decrement': return { count: state.count - 1 };
    case 'reset': return { count: 0 };
    default: throw new Error('Unknown action: ' + action.type);
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, { count: 0 });

  return (
    <div>
      <p>{state.count}</p>
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
      <button onClick={() => dispatch({ type: 'reset' })}>Reset</button>
    </div>
  );
}

useReducer + useContext together is a common lightweight pattern for sharing state and actions across a component subtree without pulling in an external library.

useRef, useContext and useReducer Hooks in React | DevNotes