DevPath · Learn to code ESPTEN

Custom hooks and performance

Custom hooks: reusable stateful logic

The problem: repeated logic

Imagine two components that need a switch (on/off). In both you would write the same thing:

const [open, setOpen] = useState(false);
const toggle = () => setOpen((v) => !v);

Copying and pasting that logic works, but it drifts out of sync over time. React's solution is custom hooks.

What a custom hook is

A custom hook is, simply, a function whose name starts with use and that uses other hooks inside. It encapsulates stateful logic and returns what the component needs:

function useToggle(initial = false) {
  const [value, setValue] = useState(initial);
  const toggle = () => setValue((v) => !v);
  return [value, toggle];
}

Now any component reuses that logic without duplicating it:

function Panel() {
  const [open, toggle] = useToggle(false);
  return (
    <div>
      <button onClick={toggle}>Show/hide</button>
      {open && <p>Visible content</p>}
    </div>
  );
}

The key point: each component that calls useToggle gets its own state, isolated. You share the logic, not the state.

Another example: useCounter

A hook can compose several hooks and expose whatever API is most convenient (an object, an array, functions...):

function useCounter(initial = 0) {
  const [count, setCount] = useState(initial);
  const increment = () => setCount((c) => c + 1);
  const reset = () => setCount(initial);
  return { count, increment, reset };
}

The rules of hooks

So that React can associate each call with its state, hooks have two unbreakable rules:

  1. Call them only at the top level. Never inside if, loops, conditionals or nested functions. The order of the calls must be the same on every render.
  2. Call them only from React components or from other custom hooks. Not from regular JavaScript functions.
// ❌ BAD: hook inside an if (the order changes between renders)
if (active) {
  const [x, setX] = useState(0);
}

// ✅ GOOD: at the top level; the condition goes inside
const [x, setX] = useState(0);
if (active) { /* use x */ }

A custom hook is just a naming convention (use...) plus these rules. There is no "magic": it is a function that organizes your hooks.

Examples

A reusable useToggle hook

function useToggle(initial) {
  const [value, setValue] = React.useState(initial);
  const toggle = () => setValue((v) => !v);
  return [value, toggle];
}
// Simulation: two "instances" have independent state
let state1 = false, state2 = true;
console.log("start:", state1, state2);
Put this into practice

DevPath is a hands-on course: you read the theory here; in the app you put it into practice with exercises that really run, offline.

Start free in the app →
Performance: why React re-renders →