Advanced Hooks — useRef, useContext, useMemo, useCallback
useRef — Mutable References
useRef returns a mutable ref object whose .current property persists across renders. Unlike state, changing a ref does not trigger a re-render. Common uses: accessing DOM elements directly, storing previous values, holding timers.
import { useRef, useState, useEffect } from 'react'
// 1. Access DOM element
function FocusInput() {
const inputRef = useRef(null)
const focusInput = () => {
inputRef.current.focus() // direct DOM access
}
return (
<div>
<input ref={inputRef} placeholder="Click button to focus" />
<button onClick={focusInput}>Focus Input</button>
</div>
)
}
// 2. Store previous value
function PreviousValue() {
const [count, setCount] = useState(0)
const prevCount = useRef(0)
useEffect(() => {
prevCount.current = count // update after render
})
return (
<div>
<p>Current: {count}, Previous: {prevCount.current}</p>
<button onClick={() => setCount(c => c + 1)}>+</button>
</div>
)
}
// 3. Store timer ID (doesn't cause re-render)
function Stopwatch() {
const [time, setTime] = useState(0)
const [running, setRunning] = useState(false)
const intervalRef = useRef(null)
const start = () => {
setRunning(true)
intervalRef.current = setInterval(() => setTime(t => t + 1), 1000)
}
const stop = () => {
setRunning(false)
clearInterval(intervalRef.current)
}
return (
<div>
<p>{time}s</p>
<button onClick={running ? stop : start}>{running ? 'Stop' : 'Start'}</button>
</div>
)
}
import { createContext, useContext, useState } from 'react'
// 1. Create context
const ThemeContext = createContext('light')
const UserContext = createContext(null)
// 2. Provide context — wrap components that need it
function App() {
const [theme, setTheme] = useState('light')
const [user] = useState({ name: 'Alice', role: 'admin' })
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
<UserContext.Provider value={user}>
<Navbar />
<Main />
</UserContext.Provider>
</ThemeContext.Provider>
)
}
// 3. Consume context — any descendant can access it
function Navbar() {
const { theme, setTheme } = useContext(ThemeContext)
const user = useContext(UserContext)
return (
<nav className={`navbar navbar-${theme}`}>
<span>Hello, {user?.name}</span>
<button onClick={() => setTheme(t => t === 'light' ? 'dark' : 'light')}>
Toggle Theme
</button>
</nav>
)
}
// Best practice: create a custom hook for context
function useTheme() {
const context = useContext(ThemeContext)
if (!context) throw new Error('useTheme must be used within ThemeProvider')
return context
}
// Usage: const { theme, setTheme } = useTheme()
import { useState, useMemo, useCallback, memo } from 'react'
// useMemo — memoize expensive calculations
function ExpensiveList({ items, filter }) {
// Only recalculates when items or filter changes
const filteredItems = useMemo(() => {
console.log('Filtering...') // won't run on every render
return items.filter(item =>
item.name.toLowerCase().includes(filter.toLowerCase())
)
}, [items, filter])
return <ul>{filteredItems.map(i => <li key={i.id}>{i.name}</li>)}</ul>
}
// useCallback — memoize functions (prevent child re-renders)
const ChildButton = memo(({ onClick, label }) => {
console.log(`${label} rendered`)
return <button onClick={onClick}>{label}</button>
})
function Parent() {
const [count, setCount] = useState(0)
const [other, setOther] = useState(0)
// Without useCallback: new function on every render → ChildButton re-renders
// With useCallback: same function reference → ChildButton skips re-render
const handleIncrement = useCallback(() => {
setCount(c => c + 1)
}, []) // no dependencies — function never changes
return (
<div>
<p>Count: {count}, Other: {other}</p>
<ChildButton onClick={handleIncrement} label="Increment" />
<button onClick={() => setOther(o => o + 1)}>Other</button>
{/* ChildButton won't re-render when "other" changes */}
</div>
)
}
// When to use useMemo/useCallback:
// ✓ Expensive calculations (sorting, filtering large arrays)
// ✓ Passing callbacks to memoized children (memo())
// ✗ Simple calculations — overhead not worth it
// ✗ Premature optimization — profile first!
Ready to Level Up Your Skills?
Explore 500+ free tutorials across 20+ languages and frameworks.