React's Real Refactor Isn't Components—It's Pulling Logic Into Custom Hooks
Teams that stop at component splitting still suffer tangled change surfaces—adding a feature means touching three or four files. Moving business logic into Hooks makes operations testable in isolation, keeps UI swaps cheap, and prevents the "300-line App.tsx" that every growing React project eventually hits.
Most React codebases split components but leave useState, CRUD, and filtering piled into a 300-line App.tsx. That only reorganizes JSX—it doesn't separate concerns. A custom Hook acts as a dedicated logic layer that owns all state and mutation functions, exposing data and callbacks to components that become pure render functions. App.tsx shrinks to a 20-line assembly point that wires Hook output to component props. The article walks through a Todo app refactor: TypeScript types define the data contract first, a useTodos Hook centralizes every operation with functional state updates, and components like TodoInput and TodoItem contain nothing but local UI state and event callbacks. Filtering logic stays in the Hook as derived state—computed, not stored—avoiding synchronization bugs. The piece also covers when to upgrade from multiple useState calls to useReducer, and gives a clear rule: only state shared across components belongs in a Hook; input-field state stays local.
The distinction between splitting components and splitting logic explains why many React codebases feel clean to read but painful to change—the real coupling is in state and operations, not JSX.
Treating derived state as a computation rather than stored state is a principle that eliminates an entire class of synchronization bugs, yet it's frequently violated in practice.
The rule of thumb for useReducer—three or more independent setState calls—gives a concrete, testable threshold rather than a vague "when state logic gets complex."