Once you understand useState and useEffect, advanced hooks help solve the next group of problems in React applications. They help you keep values across renders, share data deeply in the component tree, and optimize expensive calculations or repeated re-renders.
The important thing is to use each hook for the right job. useRef is not a replacement for state, useContext is not a complete state management library, and useMemo and useCallback are useful only when they solve a real performance problem.
| Hook | Main purpose | Triggers re-render? | Common use |
|---|---|---|---|
useRef | Store a mutable value across renders | No | DOM access, previous values, timers |
useContext | Read shared data from a provider | Yes, when context value changes | Theme, auth user, language |
useMemo | Memoize a calculated value | No | Filtering, sorting, expensive derived data |
useCallback | Memoize a function reference | No | Passing stable handlers to memoized children |
useRef stores a value that stays the same between renders. Updating a ref does not re-render the component. This makes it useful for remembering values that React does not need to display directly.
The two most common uses of useRef are accessing DOM elements and storing mutable values like timer IDs or previous values.
import { useRef } from 'react'
function FocusInput() {
const inputRef = useRef(null)
const focusField = () => {
inputRef.current.focus()
}
return (
<div>
<input ref={inputRef} placeholder="Type here" />
<button onClick={focusField}>Focus Input</button>
</div>
)
}
import { useRef, useState, useEffect } from 'react'
function PreviousValue() {
const [count, setCount] = useState(0)
const previousCount = useRef(0)
useEffect(() => {
previousCount.current = count
}, [count])
return (
<div>
<p>Current: {count}</p>
<p>Previous: {previousCount.current}</p>
<button onClick={() => setCount(count + 1)}>Increase</button>
</div>
)
}
useContext lets you read shared values without passing props through many levels of components. This is useful when the same data is needed in many places, such as theme, language, or authentication details.
Context works best for shared app-level data. If the data is needed by only one or two nearby components, normal props are often simpler.
import { createContext, useContext, useState } from 'react'
const ThemeContext = createContext(null)
export function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light')
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
)
}
export function useTheme() {
return useContext(ThemeContext)
}
import { useTheme } from './ThemeContext'
function Navbar() {
const { theme, setTheme } = useTheme()
return (
<nav>
<p>Current theme: {theme}</p>
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
Toggle Theme
</button>
</nav>
)
}
useMemo stores the result of a calculation so React does not repeat the same expensive work on every render. It is useful when the calculation takes noticeable time or when you want to avoid recreating a derived value unnecessarily.
Do not use useMemo for every small calculation. It is best when the work is expensive enough to justify caching.
import { useState, useMemo } from 'react'
function ProductList({ products }) {
const [search, setSearch] = useState('')
const filteredProducts = useMemo(() => {
return products.filter(product =>
product.name.toLowerCase().includes(search.toLowerCase())
)
}, [products, search])
return (
<div>
<input
value={search}
onChange={(e) => setSearch(e.target.value)}
placeholder="Search products"
/>
<ul>
{filteredProducts.map(product => (
<li key={product.id}>{product.name}</li>
))}
</ul>
</div>
)
}
useCallback stores a function reference so the same function can be reused between renders. This is mainly useful when passing a callback to a memoized child component.
Without useCallback, a new function is created on every render. Sometimes that is completely fine. Use it when a stable function reference actually helps performance or avoids unnecessary child re-renders.
import { useState, useCallback, memo } from 'react'
const ChildButton = memo(function ChildButton({ onClick }) {
console.log('Child rendered')
return <button onClick={onClick}>Increment</button>
})
function Parent() {
const [count, setCount] = useState(0)
const [other, setOther] = useState(0)
const handleIncrement = useCallback(() => {
setCount(prev => prev + 1)
}, [])
return (
<div>
<p>Count: {count}</p>
<p>Other: {other}</p>
<ChildButton onClick={handleIncrement} />
<button onClick={() => setOther(other + 1)}>Update Other</button>
</div>
)
}
Use useRef when you need to remember something without re-rendering. Use useContext when many nested components need the same shared value. Use useMemo when a calculated value is expensive to recompute. Use useCallback when a stable function reference helps a memoized child component.
| Situation | Recommended approach |
|---|---|
| You need to focus or measure a DOM element | Use useRef |
| You need shared theme or user information | Use useContext |
| You are repeatedly filtering or sorting a large list | Use useMemo if the work is expensive |
| A memoized child gets a new callback every render | Use useCallback |
| You are optimizing code before seeing a real issue | Avoid premature optimization |
Advanced hooks help React applications scale more cleanly. useRef remembers mutable values, useContext shares data, useMemo caches calculated values, and useCallback caches function references. Once you understand the purpose of each one, it becomes much easier to choose the right hook for the problem you are solving.
Explore 500+ free tutorials across 20+ languages and frameworks.