Custom Hooks
What are Custom Hooks?
Custom hooks are JavaScript functions that start with use and can call other hooks. They let you extract and reuse stateful logic across multiple components without duplicating code.
Custom hooks are the React way to share logic — they replace patterns like higher-order components and render props.
// hooks/useFetch.js
import { useState, useEffect } from 'react'
function useFetch(url) {
const [data, setData] = useState(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
useEffect(() => {
if (!url) return
const controller = new AbortController()
setLoading(true)
setError(null)
fetch(url, { signal: controller.signal })
.then(res => {
if (!res.ok) throw new Error(`HTTP ${res.status}`)
return res.json()
})
.then(data => { setData(data); setLoading(false) })
.catch(err => {
if (err.name !== 'AbortError') {
setError(err.message)
setLoading(false)
}
})
return () => controller.abort()
}, [url])
return { data, loading, error }
}
// Usage — clean and reusable
function UserList() {
const { data: users, loading, error } = useFetch('/api/users')
if (loading) return <p>Loading...</p>
if (error) return <p>Error: {error}</p>
return <ul>{users?.map(u => <li key={u.id}>{u.name}</li>)}</ul>
}
function PostList() {
const { data: posts, loading } = useFetch('/api/posts')
// Same hook, different URL — no code duplication!
return loading ? <p>Loading...</p> : <div>{posts?.length} posts</div>
}
// hooks/useLocalStorage.js
import { useState, useEffect } from 'react'
function useLocalStorage(key, initialValue) {
const [value, setValue] = useState(() => {
try {
const stored = localStorage.getItem(key)
return stored ? JSON.parse(stored) : initialValue
} catch {
return initialValue
}
})
useEffect(() => {
try {
localStorage.setItem(key, JSON.stringify(value))
} catch (err) {
console.error('localStorage error:', err)
}
}, [key, value])
const remove = () => {
localStorage.removeItem(key)
setValue(initialValue)
}
return [value, setValue, remove]
}
// Usage
function Settings() {
const [theme, setTheme] = useLocalStorage('theme', 'light')
const [fontSize, setFontSize] = useLocalStorage('fontSize', 16)
return (
<div>
<button onClick={() => setTheme(t => t === 'light' ? 'dark' : 'light')}>
Theme: {theme}
</button>
<input type="range" min={12} max={24} value={fontSize}
onChange={e => setFontSize(Number(e.target.value))} />
<p style={{ fontSize }}>Font size: {fontSize}px</p>
</div>
)
}
import { useState, useEffect, useCallback } from 'react'
// useDebounce — delay value update
function useDebounce(value, delay = 500) {
const [debouncedValue, setDebouncedValue] = useState(value)
useEffect(() => {
const timer = setTimeout(() => setDebouncedValue(value), delay)
return () => clearTimeout(timer)
}, [value, delay])
return debouncedValue
}
// useToggle — boolean toggle
function useToggle(initial = false) {
const [value, setValue] = useState(initial)
const toggle = useCallback(() => setValue(v => !v), [])
return [value, toggle]
}
// useWindowSize — responsive hook
function useWindowSize() {
const [size, setSize] = useState({ width: window.innerWidth, height: window.innerHeight })
useEffect(() => {
const handler = () => setSize({ width: window.innerWidth, height: window.innerHeight })
window.addEventListener('resize', handler)
return () => window.removeEventListener('resize', handler)
}, [])
return size
}
// useOnClickOutside — close dropdown/modal
function useOnClickOutside(ref, handler) {
useEffect(() => {
const listener = (e) => {
if (!ref.current || ref.current.contains(e.target)) return
handler(e)
}
document.addEventListener('mousedown', listener)
return () => document.removeEventListener('mousedown', listener)
}, [ref, handler])
}
// Usage examples
function SearchBar() {
const [query, setQuery] = useState('')
const debouncedQuery = useDebounce(query, 300)
const { width } = useWindowSize()
const [isOpen, toggleOpen] = useToggle(false)
useEffect(() => {
if (debouncedQuery) console.log('Searching:', debouncedQuery)
}, [debouncedQuery])
return (
<div>
<input value={query} onChange={e => setQuery(e.target.value)} />
<p>Window: {width}px | {width < 768 ? 'Mobile' : 'Desktop'}</p>
<button onClick={toggleOpen}>{isOpen ? 'Close' : 'Open'}</button>
</div>
)
}
Ready to Level Up Your Skills?
Explore 500+ free tutorials across 20+ languages and frameworks.