HomeAbout Me

Advanced React APIs: Custom Hooks

By Daniel Nguyen
Published in React JS
June 13, 2025
2 min read
Advanced React APIs: Custom Hooks

🧠 Demystifying Custom Hooks, Memoization, and useCallback in React

React’s flexibility empowers developers to write expressive, component-driven code. But with that power comes architectural responsibility—especially when abstracting shared logic. This post will walk you through:

  • What custom hooks really are
  • How memoization helps prevent performance pitfalls
  • Why useCallback matters
  • A real-world example: extracting reusable search param logic into a useSearchParams custom hook

🔧 What Are Custom Hooks?

A custom hook is simply a function that uses other hooks. Nothing more, nothing less.

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

We can then use it like this:

function Counter() {
const { count, increment } = useCount()
return <button onClick={increment}>{count}</button>
}

Using the use prefix is not just a naming convention—it ensures React treats it like a hook and applies the rules of hooks properly (e.g., only calling hooks at the top level).

🧠 Why use custom hooks? They encapsulate logic so you can reuse it across components without repeating yourself.


🌀 But Be Careful: Referential Equality Matters

Let’s say you’re using a function returned from a hook (like increment) inside a useEffect:

function Counter() {
const { count, increment } = useCount()
useEffect(() => {
const id = setInterval(increment, 1000)
return () => clearInterval(id)
}, [increment])
return <div>{count}</div>
}

Even though increment does the same thing each time, its identity changes on every render. That means your useEffect re-runs every time.

This is bad because:

  • It clears and recreates the interval unnecessarily
  • It causes unnecessary side-effect churn

💡 Solution: Memoize With useCallback

useCallback lets you tell React to “remember” the same function instance as long as its dependencies haven’t changed.

function useCount() {
const [count, setCount] = useState(0)
const increment = useCallback(() => setCount(c => c + 1), [])
return { count, increment }
}

Now increment stays stable across renders.

You can think of useCallback as a cache:

let lastCallback
function useCallback(callback, deps) {
if (depsChanged(deps)) {
lastCallback = callback
return callback
}
return lastCallback
}

📦 A Primer on Memoization

Memoization is a performance optimization technique: instead of recalculating values, we store and reuse results for the same inputs.

Basic memoization:

const cache = {}
function addOne(num: number) {
if (cache[num] === undefined) {
cache[num] = num + 1
}
return cache[num]
}

Generic memoize function:

function memoize<Arg, Result>(cb: (arg: Arg) => Result) {
const cache: Record<string, Result> = {}
return function(arg: Arg) {
if (cache[arg as any] === undefined) {
cache[arg as any] = cb(arg)
}
return cache[arg as any]
}
}

🧠 useCallback vs useMemo

Both are for memoization.

  • useCallback(fn, deps) is shorthand for useMemo(() => fn, deps)
  • useCallback returns a memoized function
  • useMemo returns a memoized value
const increment = useCallback(() => setCount(c => c + 1), [])
// is equivalent to:
const increment = useMemo(() => () => setCount(c => c + 1), [])

Always remember: dependencies must be stable, or your memoization will be broken.


🧪 Let’s Build a Reusable useSearchParams Hook

Suppose you have some logic in your App component that manipulates the browser’s search params:

const [searchParams, setSearchParams] = useState(...)

You want to extract this logic into a custom hook so other components can reuse it.

function useSearchParams(): [
URLSearchParams,
(newParams: Record<string, string>) => void
] {
const [params, setParams] = useState(() => new URLSearchParams(window.location.search))
const setSearchParams = useCallback((newParams: Record<string, string>) => {
const newSearchParams = new URLSearchParams()
for (const key in newParams) {
newSearchParams.set(key, newParams[key])
}
const newUrl = `${window.location.pathname}?${newSearchParams.toString()}`
window.history.pushState(null, '', newUrl)
setParams(newSearchParams)
}, [])
return [params, setSearchParams]
}

Now use it in your component like this:

function App() {
const [searchParams, setSearchParams] = useSearchParams()
function handleClick() {
setSearchParams({ query: 'hello' })
}
return <button onClick={handleClick}>Search</button>
}

Why wrap setSearchParams with useCallback? So it stays referentially stable, in case someone needs to use it inside a useEffect.


🤔 Should You Wrap Everything in useCallback?

No! Overusing useCallback can actually hurt performance by cluttering your code and forcing React to do unnecessary dependency checks.

Only use it when:

  • You’re passing a function to a useEffect or useMemo dependency array
  • You’re passing a function to memoized children (React.memo)
  • You want to avoid re-creating functions unnecessarily in hot paths

📚 Read more here


🧵 TL;DR

  • Custom hooks are just functions that use hooks—great for reuse.
  • Memoization avoids recomputation and preserves reference equality.
  • useCallback memoizes functions so they’re stable across renders.
  • Use useCallback when it matters—especially in reusable hooks.
  • Don’t wrap everything! Be intentional.

📘 Further Learning


🎉 Congratulations! You’ve taken a deep dive into memoization, abstraction, and dependency management in React.


Tags

#AdvancedReactAPIs

Share

Previous Article
Advanced React APIs: State Optimization

Table Of Contents

1
🔧 What Are Custom Hooks?
2
🌀 But Be Careful: Referential Equality Matters
3
💡 Solution: Memoize With useCallback
4
📦 A Primer on Memoization
5
🧠 useCallback vs useMemo
6
🧪 Let’s Build a Reusable useSearchParams Hook
7
🤔 Should You Wrap Everything in useCallback?
8
🧵 TL;DR
9
📘 Further Learning

Related Posts

Advanced React APIs: Sync Exernal Store
June 19, 2025
2 min
© 2025, All Rights Reserved.
Powered By

Quick Links

About Me

Legal Stuff

Social Media