Building Custom Hooks in React

Hero image for Building Custom Hooks in React. Image by Haberdoedas.
Hero image for 'Building Custom Hooks in React.' Image by Haberdoedas.

One of the most useful ideas in modern React is that stateful logic can be reused without reusing markup.

That is the real value of custom hooks.

Before hooks, reusing stateful behaviour often pushed developers towards patterns like higherorder components or render props. Those patterns still have their place, but custom hooks made a common problem much easier to solve. If several components need the same stateful behaviour, we can extract that behaviour into a hook and call it from each component.

The important phrase there is stateful behaviour, not UI.


A Custom Hook is Just a Function That Uses Hooks

At its simplest:

const useToggle = (  initialValue = false,): [boolean, () => void] => {  const [value, setValue] = useState(initialValue);  const toggle = (): void => {    setValue((currentValue) => !currentValue);  };  return [value, toggle];};

Now any component can use that logic:

const Panel = (): JSX.Element => {  const [isOpen, toggleOpen] = useToggle(false);  return <button onClick={toggleOpen}>{isOpen ? 'Hide' : 'Show'}</button>;};

That is a small example, but it shows the pattern clearly.


The Hook is Sharing Logic, Not State

This distinction matters a great deal.

If two components both call useToggle(), they do not magically receive the same shared toggle state. Each component gets its own separate stateful instance of that logic.

Custom hooks are like extracted behaviour templates. They encapsulate repeated stateful code, but each caller still owns its own state.

That is why custom hooks are not a replacement for context or shared application state. They solve a different problem.


A Realistic Example: Window Width

Suppose several components need to respond to viewport width. Repeating the same effect and event listener in each component would be clumsy.

That logic can be extracted:

const useWindowWidth = (): number => {  const [width, setWidth] = useState(window.innerWidth);  useEffect(() => {    const handleResize = (): void => {      setWidth(window.innerWidth);    };    window.addEventListener('resize', handleResize);    return () => {      window.removeEventListener('resize', handleResize);    };  }, []);  return width;};

Now components can simply ask for the width:

const Layout = (): JSX.Element => {  const width = useWindowWidth();  return <p>Viewport width: {width}</p>;};

The stateful logic lives in one place, and the consuming component stays focused on rendering.


Good Custom Hooks Usually Do One Coherent Job

The strongest hooks are often narrow:

  • detect the window width
  • manage a toggle
  • keep the document title in sync
  • store the previous value
  • manage a fetch request

When a hook starts handling three unrelated concerns at once, it often stops being reusable and starts becoming a miniframework hidden inside a function.


Naming matters because the use prefix is not cosmetic

Hooks are expected to start with use for a reason. The name tells React tooling and human readers that hook rules apply.

So:

  • useWindowWidth
  • useToggle
  • usePrevious

are good names.

Calling the same function getWindowWidthState would obscure what it really is.


Custom Hooks Help Remove Duplication from Effects

This is one of the places they shine most.

Effects often bring:

  • setup
  • teardown
  • subscriptions
  • timers
  • DOM listeners

When that same pattern appears in multiple components, extracting it into a custom hook can make the code much easier to trust. Instead of repeating the effect and hoping every copy stays in sync, we centralise the behaviour.


They Also Make Component Bodies Easier to Read

Consider a component doing this inline:

  • state for whether data is loading
  • state for the result
  • state for the error
  • an effect to fetch the data
  • cleanup logic

That is a lot of plumbing mixed in with markup.

If that behaviour becomes a hook such as useProducts(), the component body becomes more declarative:

const ProductList = (): JSX.Element => {  const { products, isLoading, error } = useProducts();  if (isLoading) {    return <p>Loading...</p>;  }  if (error) {    return <p>Could not load products.</p>;  }  return (    <ul>      {products.map((product) => (        <li key={product.id}>{product.name}</li>      ))}    </ul>  );};

The component now reads more like UI, and the stateful mechanics move behind a clearer abstraction.


A Hook Should Expose a Useful Interface, Not Every Internal Detail

This is where design judgement enters.

A good custom hook returns the values and functions the consuming component actually needs, not a random dump of internal implementation pieces.

That may mean returning:

  • a tuple
  • an object
  • a small set of callbacks

The shape should reflect how the hook is meant to be used.


The Rules of Hooks Still apply

Because a custom hook is still using hooks internally, the normal rules remain:

  • call hooks at the top level
  • do not call them conditionally
  • only call hooks from React components or other hooks

Custom hooks are not loopholes around those rules. They are one of the intended ways to compose them.


Not Every Repeated Code Block Needs to Become a Custom Hook

If logic is purely synchronous and stateless, a plain helper function may be enough.

The hook pattern is most useful when the repeated logic genuinely involves React's stateful or effectful features. Otherwise, wrapping ordinary utility code in a hook just because it lives near React can make the design more confusing.


Custom Hooks are One of React's Cleaner Reuse Mechanisms

That is why they became popular so quickly.

They let us take repeated stateful behaviour and give it a name, an interface, and a home of its own. The component consuming the hook becomes easier to read, and the logic becomes easier to reuse and evolve.

The main thing to remember is what problem they solve. Custom hooks do not share state globally, and they do not render UI for us. They package recurring React logic in a way that feels natural inside function components.

Used that way, they are one of the most practical additions React has made.


Categories:

  1. Development
  2. Front‑End Development
  3. Guides
  4. JavaScript
  5. React