The list of the top 10 custom hooks, with code examples


Never miss an update
Subscribe to receive news and special offers.
By subscribing you agree to our Privacy Policy.
React custom hooks allow you to extract and reuse logic across components, making your code more maintainable, modular, and readable. In this guide, we cover 10 powerful custom hooks that every React developer should have in their toolbox — complete with examples and common use cases.
useFetchFetch and manage data effortlessly with built-in support for loading and error states.
// hooks/useFetch.js
import { useState, useEffect } from 'react';
function useFetch(url) {
// States to store data, loading status, and errors
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
// Async function to fetch data
const fetchData = async () => {
try {
const res = await fetch(url);
const json = await res.json();
setData(json); // Store data if request succeeds
} catch (err) {
setError(err); // Capture error if request fails
} finally {
setLoading(false); // Mark loading as complete in all cases
}
};
fetchData();
}, [url]); // Re-trigger if URL changes
return { data, loading, error };
}
export default useFetch;Usage Example:
// components/UserProfile.jsx
import useFetch from '../hooks/useFetch';
function UserProfile({ userId }) {
const { data, loading, error } = useFetch(`/api/users/${userId}`);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<div>
<h1>{data.name}</h1>
<p>{data.email}</p>
</div>
);
}useLocalStoragePersist state in localStorage for browser-based storage. Perfect for saving user preferences or application state between sessions.
// hooks/useLocalStorage.js
import { useState } from 'react';
function useLocalStorage(key, initialValue) {
// Initialize state with stored value or initial value
const [storedValue, setStoredValue] = useState(() => {
if (typeof window === 'undefined') {
return initialValue; // Return initial value on server-side
}
try {
// Get value from localStorage
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
console.error(error);
return initialValue;
}
});
// Function to update value in state and localStorage
const setValue = (value) => {
try {
const valueToStore = value instanceof Function ? value(storedValue) : value;
setStoredValue(valueToStore);
// Save to localStorage (client-side only)
if (typeof window !== 'undefined') {
window.localStorage.setItem(key, JSON.stringify(valueToStore));
}
} catch (error) {
console.error(error);
}
};
return [storedValue, setValue];
}
export default useLocalStorage;useToggleQuickly toggle between true and false — great for modals, dropdowns, or dark mode.
// hooks/useToggle.js
import { useState } from 'react';
function useToggle(initialValue = false) {
// State to track current value
const [state, setState] = useState(initialValue);
// Function to toggle state
const toggle = () => setState(prev => !prev);
return [state, toggle];
}
export default useToggle;Usage Example:
// Simple example for a dropdown menu
const [isOpen, toggle] = useToggle();
return (
<>
<button onClick={toggle}>
{isOpen ? 'Close' : 'Open'} Menu
</button>
{isOpen && <div className="dropdown-menu">Menu Content</div>}
</>
);useDebounceDelay updates to prevent unnecessary calls, perfect for search inputs and auto-saving.
// hooks/useDebounce.js
import { useState, useEffect } from 'react';
function useDebounce(value, delay) {
// State to store the delayed value
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
// Set a delay before updating the value
const handler = setTimeout(() => setDebouncedValue(value), delay);
// Clean up timeout if value changes before delay
return () => clearTimeout(handler);
}, [value, delay]);
return debouncedValue;
}
export default useDebounce;useIntervalExecute a callback repeatedly at specified intervals — cleanly and React-friendly.
// hooks/useInterval.js
import { useRef, useEffect } from 'react';
function useInterval(callback, delay) {
const savedCallback = useRef();
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
useEffect(() => {
if (delay === null) return;
const id = setInterval(() => savedCallback.current(), delay);
return () => clearInterval(id);
}, [delay]);
}
export default useInterval;useWindowSizeTrack the current window size for responsive rendering.
// hooks/useWindowSize.js
import { useState, useEffect } from 'react';
function useWindowSize() {
const [size, setSize] = useState({
width: typeof window !== 'undefined' ? window.innerWidth : 0,
height: typeof window !== 'undefined' ? window.innerHeight : 0,
});
useEffect(() => {
const handleResize = () => setSize({
width: window.innerWidth,
height: window.innerHeight,
});
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return size;
}
export default useWindowSize;useWebSocketAdd real-time capabilities with automatic reconnection and message handling.
// hooks/useWebSocket.js
import { useState, useEffect, useRef, useCallback } from 'react';
function useWebSocket(url, options = {}) {
const [data, setData] = useState(null);
const [status, setStatus] = useState('CONNECTING');
const [error, setError] = useState(null);
const [reconnectAttempt, setReconnectAttempt] = useState(0);
const ws = useRef(null);
const reconnectTimeoutRef = useRef(null);
const {
reconnectInterval = 2000,
maxReconnectAttempts = 5,
onOpen,
onClose,
onMessage,
onError,
protocols
} = options;
// Function to send a message
const sendMessage = useCallback((message) => {
if (ws.current && ws.current.readyState === WebSocket.OPEN) {
ws.current.send(typeof message === 'string' ? message : JSON.stringify(message));
return true;
}
return false;
}, []);
// Function to connect
const connect = useCallback(() => {
// Clear any existing connection
if (ws.current) {
ws.current.close();
}
// Create a new WebSocket connection
try {
ws.current = new WebSocket(url, protocols);
setStatus('CONNECTING');
// Connection opened
ws.current.onopen = (event) => {
setStatus('OPEN');
setReconnectAttempt(0);
if (onOpen) onOpen(event);
};
// Listen for messages
ws.current.onmessage = (event) => {
try {
const parsedData = JSON.parse(event.data);
setData(parsedData);
if (onMessage) onMessage(parsedData, event);
} catch (e) {
// Handle non-JSON messages
setData(event.data);
if (onMessage) onMessage(event.data, event);
}
};
// Connection closed
ws.current.onclose = (event) => {
setStatus('CLOSED');
if (onClose) onClose(event);
// Try to reconnect
if (reconnectAttempt < maxReconnectAttempts) {
reconnectTimeoutRef.current = setTimeout(() => {
setReconnectAttempt(prev => prev + 1);
connect();
}, reconnectInterval);
} else {
setError(new Error('Maximum reconnect attempts reached'));
}
};
// Connection error
ws.current.onerror = (event) => {
setStatus('ERROR');
setError(event);
if (onError) onError(event);
};
} catch (error) {
setStatus('ERROR');
setError(error);
if (onError) onError(error);
}
}, [url, protocols, reconnectAttempt, maxReconnectAttempts, reconnectInterval, onOpen, onClose, onMessage, onError]);
// Connect on mount, reconnect if URL changes
useEffect(() => {
connect();
// Clean up on unmount
return () => {
if (reconnectTimeoutRef.current) {
clearTimeout(reconnectTimeoutRef.current);
}
if (ws.current) {
ws.current.close();
}
};
}, [connect, url]);
return {
data,
status,
error,
sendMessage,
reconnectAttempt,
isConnecting: status === 'CONNECTING',
isOpen: status === 'OPEN',
isClosed: status === 'CLOSED',
isError: status === 'ERROR'
};
}
export default useWebSocket;Usage Example:
// components/ChatRoom.jsx
import { useState } from 'react';
import useWebSocket from '../hooks/useWebSocket';
function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');
const [messages, setMessages] = useState([]);
const {
sendMessage,
status,
data,
isOpen
} = useWebSocket(`wss://chat.example.com/room/${roomId}`, {
onMessage: (data) => {
setMessages(prev => [...prev, data]);
}
});
const handleSend = (e) => {
e.preventDefault();
if (message.trim() && isOpen) {
sendMessage(JSON.stringify({ text: message, timestamp: Date.now() }));
setMessage('');
}
};
return (
<div className="chat-room">
<div className="status">
Connection status: {status}
</div>
<div className="messages">
{messages.map((msg, index) => (
<div key={index} className="message">
<span className="author">{msg.author}:</span>
<span className="text">{msg.text}</span>
<span className="time">{new Date(msg.timestamp).toLocaleTimeString()}</span>
</div>
))}
</div>
<form onSubmit={handleSend}>
<input
type="text"
value={message}
onChange={(e) => setMessage(e.target.value)}
placeholder="Type a message..."
disabled={!isOpen}
/>
<button type="submit" disabled={!isOpen}>Send</button>
</form>
</div>
);
}Key Features:
useOnClickOutsideDetect clicks outside an element — commonly used for closing modals or dropdowns.
// hooks/useOnClickOutside.js
import { useEffect } from 'react';
function useOnClickOutside(ref, handler) {
useEffect(() => {
const listener = event => {
// Do nothing if clicking ref's element or descendent elements
if (!ref.current || ref.current.contains(event.target)) return;
handler(event);
};
document.addEventListener('mousedown', listener);
document.addEventListener('touchstart', listener);
return () => {
document.removeEventListener('mousedown', listener);
document.removeEventListener('touchstart', listener);
};
}, [ref, handler]);
}
export default useOnClickOutside;useHoverTrack hover state for a DOM element.
// hooks/useHover.js
import { useState, useRef, useEffect } from 'react';
function useHover() {
const [hovered, setHovered] = useState(false);
const ref = useRef(null);
useEffect(() => {
const node = ref.current;
if (!node) return;
const handleMouseOver = () => setHovered(true);
const handleMouseOut = () => setHovered(false);
node.addEventListener('mouseover', handleMouseOver);
node.addEventListener('mouseout', handleMouseOut);
return () => {
node.removeEventListener('mouseover', handleMouseOver);
node.removeEventListener('mouseout', handleMouseOut);
};
}, []);
return [ref, hovered];
}
export default useHover;useMediaQueryUse media queries in JS to react to device or screen changes.
// hooks/useMediaQuery.js
import { useState, useEffect } from 'react';
function useMediaQuery(query) {
const [matches, setMatches] = useState(false);
useEffect(() => {
// Create a media query list
const media = window.matchMedia(query);
// Set initial state
setMatches(media.matches);
// Define and add event listener
const handler = (e) => setMatches(e.matches);
media.addEventListener('change', handler);
// Clean up
return () => media.removeEventListener('change', handler);
}, [query]);
return matches;
}
export default useMediaQuery;Usage Example:
// Using media queries for responsive design
const isMobile = useMediaQuery('(max-width: 768px)');
return (
<div className={isMobile ? 'mobile-layout' : 'desktop-layout'}>
{isMobile ? <MobileMenu /> : <DesktopMenu />}
</div>
);Custom hooks are one of the most powerful features in React, allowing you to extract and reuse stateful logic between components. The hooks covered in this guide will help you build more maintainable, efficient, and responsive React applications while reducing code duplication.
Feel free to create your own custom hooks when you encounter specific problems in your application.
Happy coding! 🚀