DesignDev.io logoDesignDev.io logo

  1. Home
  2. /From the Builder
  3. /Did You Know useCallback Can Actually Do This?
← Back to home

Did You Know useCallback Can Actually Do This?

Muhammad Athar

Muhammad Athar

Featured
Featured
Cover Image for Did You Know useCallback Can Actually Do This?
Muhammad Athar

Muhammad Athar

January 5, 2026·4 min read
Updated April 26, 2026
Series:From the Builder
Level:Beginner
Tags:reactweb
Assumes:Basic React hooks familiarity, has read or skimmed the useEffect explainer

If you've been reading the React Foundations series and kept seeing useCallback appear in custom hooks and effect dependency arrays without a clear explanation of what it actually does — this is that explanation. With a real dropdown component you can build today.


If you've been following along with Alex Chen's How to Build a Custom Hook That Actually Earns Its Abstraction or our own useEffect, What Is It Really? you've seen useCallback show up without much ceremony.

In the custom hooks article, there's this line inside useLocalStorage:

And if you've been reading carefully you might have thought — I understand what's happening here, but I don't quite understand why useCallback is wrapping that function. Why not just define it normally?

That's exactly what this article answers.


Functions are recreated on every render

Here's the thing about JavaScript functions inside React components that trips up almost every developer at some point.

Every time a React component re-renders, every function defined inside it is created fresh. New reference. New memory address. Even if the code is identical.

Most of the time this doesn't matter. The function does its job and gets garbage collected. But it becomes a problem in two specific situations:

  1. When the function is a dependency in a useEffect array
  2. When the function is passed as a prop to a component wrapped in React.memo

In both cases, a new function reference on every render means the effect re-runs or the memoized component re-renders — even when nothing meaningful changed.

useCallback fixes this by giving you back the same function reference between renders, unless its dependencies change.


The basic shape

Same structure as useEffect — a function and a dependency array. The difference: useEffect runs a function after render. useCallback returns a function that stays stable between renders.

React will only create a new function when something in the dependency array changes. Otherwise it hands back the same reference it gave you last time.


The useEffect dependency problem

Here's the scenario where useCallback earns its place most clearly.

You have a component that fetches data. The fetch logic is in a function. You want to call that function inside a useEffect. So you add the function to the dependency array — because the linter tells you to, and the linter is right.

This creates an infinite loop. The component renders → fetchUser is created → useEffect runs → setUser triggers a re-render → fetchUser is created again → useEffect runs again → forever.

useCallback breaks the loop:

Now fetchUser only gets a new reference when userId changes. The effect only re-runs when userId changes. The loop is broken.


Building something real: a dropdown component

Here's a complete accessible dropdown menu that demonstrates useCallback in a context you'll actually encounter — event handlers that need to be stable because they're attached to the document.

You can use it like this:

Two places useCallback is doing real work here:

handleClickOutside — this function gets added to document as an event listener inside a useEffect. Without useCallback, every render would produce a new function reference, and the effect would remove and re-add the listener constantly. With useCallback and an empty dependency array, the same function reference is reused for the lifetime of the component.

handleSelect — this function depends on onChange, which comes from the parent. useCallback ensures that if the parent re-renders but onChange hasn't actually changed, handleSelect stays stable too.


When useCallback is not the answer

useCallback is not a general performance tool. Wrapping every function in your component with it actually makes things slightly slower — React has to store the previous function, compare dependencies, and decide whether to return the cached version. That overhead exists even when the memoization never helps.

The two cases where it genuinely earns its place:

  1. A function is in a useEffect dependency array — use useCallback to prevent the effect re-running on every render
  2. A function is passed as a prop to a React.memo component — use useCallback to prevent the memoized component re-rendering unnecessarily

Everything else — event handlers on regular HTML elements, functions used only inside the component, callbacks that aren't dependencies — can be defined without useCallback.


The pattern to remember

Same function. Different identity. useCallback is about referential stability, not about making the function run faster.


Where this leads

useMemo — useCallback memoizes a function. useMemo memoizes a value. Same idea, different shape. Coming up next in Make Use of useMemo Like You Mean It.

React.memo — the component-level counterpart to useCallback. Wraps a component so it only re-renders when its props change. useCallback and React.memo are most useful together — stable function references passed to memoized components. Alex covers this in Why React.memo Doesn't Always Help.

useRef — another tool for keeping a stable reference across renders, but for values rather than functions. We'll get to it in the next "From the Builder" article.


This article was created for understanding the React Foundations series — particularly How to Build a Custom Hook That Actually Earns Its Abstraction and useEffect, What Is It Really?. If a function in a dependency array has ever caused you an infinite loop, now you know why — and how to fix it.

Muhammad Athar is the founder and engineer behind DesignDev.io. He writes the "From the Builder" series — concept explainers triggered by the site's own articles, always grounded in something you can actually build.

Photo by JJ Ying on Unsplash


More Stuff

Publishing Your First React Component to npm — the Complete Setup
April 1, 2026·3 min read

Publishing Your First React Component to npm — the Complete Setup

Most tutorials stop at "it works on my machine." This one doesn't. Publishing to npm is where your library becomes real — installable, versioned, and usable by anyone. Here's the complete setup, including the fields most developers get wrong in package.json and how to inspect what you're actually shipping before it goes live.

Muhammad Athar

Muhammad Athar

How to structure a UI library that scales — folders, exports, and naming conventions
April 1, 2026·3 min read

How to structure a UI library that scales — folders, exports, and naming conventions

The folder structure you pick on day one is the one you'll live with across 40 components. Get it wrong and you'll be fighting your own codebase by article 10. Get it right and adding a new component takes five minutes and follows a pattern every contributor can learn in one sitting.

Muhammad Athar

Muhammad Athar

DesignDev.io logoDesignDev.io logo

Explore

  • Home
  • Search
  • Authors
  • Series
  • About Us

Account

  • Sign in

Legal

  • Privacy Policy
  • Terms of Service

© 2026 DesignDev.io · All rights reserved

Built with Next.js · Tailwind · Sanity · Vercel