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

Top 10 Custom Hooks for Every React Developer
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.
1. useFetch
Fetch and manage data effortlessly with built-in support for loading and error states.
1// hooks/useFetch.js
2import { useState, useEffect } from 'react';
3
4function useFetch(url) {
5 // States to store data, loading status, and errors
6 const [data, setData] = useState(null);
7 const [loading, setLoading] = useState(true);
8 const [error, setError] = useState(null);
9
10 useEffect(() => {
11 // Async function to fetch data
12 const fetchData = async () => {
13 try {
14 const res = await fetch(url);
15 const json = await res.json();
16 setData(json); // Store data if request succeeds
17 } catch (err) {
18 setError(err); // Capture error if request fails
19 } finally {
20 setLoading(false); // Mark loading as complete in all cases
21 }
22 };
23 fetchData();
24 }, [url]); // Re-trigger if URL changes
25
26 return { data, loading, error };
27}
28
29export default useFetch;
Usage Example:
1// components/UserProfile.jsx
2import useFetch from '../hooks/useFetch';
3
4function UserProfile({ userId }) {
5 const { data, loading, error } = useFetch(`/api/users/${userId}`);
6
7 if (loading) return <div>Loading...</div>;
8 if (error) return <div>Error: {error.message}</div>;
9
10 return (
11 <div>
12 <h1>{data.name}</h1>
13 <p>{data.email}</p>
14 </div>
15 );
16}
2. useLocalStorage
Persist state in localStorage for browser-based storage. Perfect for saving user preferences or application state between sessions.
1// hooks/useLocalStorage.js
2import { useState } from 'react';
3
4function useLocalStorage(key, initialValue) {
5 // Initialize state with stored value or initial value
6 const [storedValue, setStoredValue] = useState(() => {
7 if (typeof window === 'undefined') {
8 return initialValue; // Return initial value on server-side
9 }
10
11 try {
12 // Get value from localStorage
13 const item = window.localStorage.getItem(key);
14 return item ? JSON.parse(item) : initialValue;
15 } catch (error) {
16 console.error(error);
17 return initialValue;
18 }
19 });
20
21 // Function to update value in state and localStorage
22 const setValue = (value) => {
23 try {
24 const valueToStore = value instanceof Function ? value(storedValue) : value;
25 setStoredValue(valueToStore);
26
27 // Save to localStorage (client-side only)
28 if (typeof window !== 'undefined') {
29 window.localStorage.setItem(key, JSON.stringify(valueToStore));
30 }
31 } catch (error) {
32 console.error(error);
33 }
34 };
35
36 return [storedValue, setValue];
37}
38
39export default useLocalStorage;
3. useToggle
Quickly toggle between true
and false
— great for modals, dropdowns, or dark mode.
1// hooks/useToggle.js
2import { useState } from 'react';
3
4function useToggle(initialValue = false) {
5 // State to track current value
6 const [state, setState] = useState(initialValue);
7 // Function to toggle state
8 const toggle = () => setState(prev => !prev);
9 return [state, toggle];
10}
11
12export default useToggle;
Usage Example:
1// Simple example for a dropdown menu
2const [isOpen, toggle] = useToggle();
3
4return (
5 <>
6 <button onClick={toggle}>
7 {isOpen ? 'Close' : 'Open'} Menu
8 </button>
9 {isOpen && <div className="dropdown-menu">Menu Content</div>}
10 </>
11);
4. useDebounce
Delay updates to prevent unnecessary calls, perfect for search inputs and auto-saving.
1// hooks/useDebounce.js
2import { useState, useEffect } from 'react';
3
4function useDebounce(value, delay) {
5 // State to store the delayed value
6 const [debouncedValue, setDebouncedValue] = useState(value);
7
8 useEffect(() => {
9 // Set a delay before updating the value
10 const handler = setTimeout(() => setDebouncedValue(value), delay);
11 // Clean up timeout if value changes before delay
12 return () => clearTimeout(handler);
13 }, [value, delay]);
14
15 return debouncedValue;
16}
17
18export default useDebounce;
5. useInterval
Execute a callback repeatedly at specified intervals — cleanly and React-friendly.
1// hooks/useInterval.js
2import { useRef, useEffect } from 'react';
3
4function useInterval(callback, delay) {
5 const savedCallback = useRef();
6
7 useEffect(() => {
8 savedCallback.current = callback;
9 }, [callback]);
10
11 useEffect(() => {
12 if (delay === null) return;
13 const id = setInterval(() => savedCallback.current(), delay);
14 return () => clearInterval(id);
15 }, [delay]);
16}
17
18export default useInterval;
6. useWindowSize
Track the current window size for responsive rendering.
1// hooks/useWindowSize.js
2import { useState, useEffect } from 'react';
3
4function useWindowSize() {
5 const [size, setSize] = useState({
6 width: typeof window !== 'undefined' ? window.innerWidth : 0,
7 height: typeof window !== 'undefined' ? window.innerHeight : 0,
8 });
9
10 useEffect(() => {
11 const handleResize = () => setSize({
12 width: window.innerWidth,
13 height: window.innerHeight,
14 });
15 window.addEventListener('resize', handleResize);
16 return () => window.removeEventListener('resize', handleResize);
17 }, []);
18
19 return size;
20}
21
22export default useWindowSize;
7. useWebSocket
Add real-time capabilities with automatic reconnection and message handling.
1// hooks/useWebSocket.js
2import { useState, useEffect, useRef, useCallback } from 'react';
3
4function useWebSocket(url, options = {}) {
5 const [data, setData] = useState(null);
6 const [status, setStatus] = useState('CONNECTING');
7 const [error, setError] = useState(null);
8 const [reconnectAttempt, setReconnectAttempt] = useState(0);
9
10 const ws = useRef(null);
11 const reconnectTimeoutRef = useRef(null);
12
13 const {
14 reconnectInterval = 2000,
15 maxReconnectAttempts = 5,
16 onOpen,
17 onClose,
18 onMessage,
19 onError,
20 protocols
21 } = options;
22
23 // Function to send a message
24 const sendMessage = useCallback((message) => {
25 if (ws.current && ws.current.readyState === WebSocket.OPEN) {
26 ws.current.send(typeof message === 'string' ? message : JSON.stringify(message));
27 return true;
28 }
29 return false;
30 }, []);
31
32 // Function to connect
33 const connect = useCallback(() => {
34 // Clear any existing connection
35 if (ws.current) {
36 ws.current.close();
37 }
38
39 // Create a new WebSocket connection
40 try {
41 ws.current = new WebSocket(url, protocols);
42 setStatus('CONNECTING');
43
44 // Connection opened
45 ws.current.onopen = (event) => {
46 setStatus('OPEN');
47 setReconnectAttempt(0);
48 if (onOpen) onOpen(event);
49 };
50
51 // Listen for messages
52 ws.current.onmessage = (event) => {
53 try {
54 const parsedData = JSON.parse(event.data);
55 setData(parsedData);
56 if (onMessage) onMessage(parsedData, event);
57 } catch (e) {
58 // Handle non-JSON messages
59 setData(event.data);
60 if (onMessage) onMessage(event.data, event);
61 }
62 };
63
64 // Connection closed
65 ws.current.onclose = (event) => {
66 setStatus('CLOSED');
67 if (onClose) onClose(event);
68
69 // Try to reconnect
70 if (reconnectAttempt < maxReconnectAttempts) {
71 reconnectTimeoutRef.current = setTimeout(() => {
72 setReconnectAttempt(prev => prev + 1);
73 connect();
74 }, reconnectInterval);
75 } else {
76 setError(new Error('Maximum reconnect attempts reached'));
77 }
78 };
79
80 // Connection error
81 ws.current.onerror = (event) => {
82 setStatus('ERROR');
83 setError(event);
84 if (onError) onError(event);
85 };
86 } catch (error) {
87 setStatus('ERROR');
88 setError(error);
89 if (onError) onError(error);
90 }
91 }, [url, protocols, reconnectAttempt, maxReconnectAttempts, reconnectInterval, onOpen, onClose, onMessage, onError]);
92
93 // Connect on mount, reconnect if URL changes
94 useEffect(() => {
95 connect();
96
97 // Clean up on unmount
98 return () => {
99 if (reconnectTimeoutRef.current) {
100 clearTimeout(reconnectTimeoutRef.current);
101 }
102
103 if (ws.current) {
104 ws.current.close();
105 }
106 };
107 }, [connect, url]);
108
109 return {
110 data,
111 status,
112 error,
113 sendMessage,
114 reconnectAttempt,
115 isConnecting: status === 'CONNECTING',
116 isOpen: status === 'OPEN',
117 isClosed: status === 'CLOSED',
118 isError: status === 'ERROR'
119 };
120}
121
122export default useWebSocket;
Usage Example:
1// components/ChatRoom.jsx
2import { useState } from 'react';
3import useWebSocket from '../hooks/useWebSocket';
4
5function ChatRoom({ roomId }) {
6 const [message, setMessage] = useState('');
7 const [messages, setMessages] = useState([]);
8
9 const {
10 sendMessage,
11 status,
12 data,
13 isOpen
14 } = useWebSocket(`wss://chat.example.com/room/${roomId}`, {
15 onMessage: (data) => {
16 setMessages(prev => [...prev, data]);
17 }
18 });
19
20 const handleSend = (e) => {
21 e.preventDefault();
22 if (message.trim() && isOpen) {
23 sendMessage(JSON.stringify({ text: message, timestamp: Date.now() }));
24 setMessage('');
25 }
26 };
27
28 return (
29 <div className="chat-room">
30 <div className="status">
31 Connection status: {status}
32 </div>
33
34 <div className="messages">
35 {messages.map((msg, index) => (
36 <div key={index} className="message">
37 <span className="author">{msg.author}:</span>
38 <span className="text">{msg.text}</span>
39 <span className="time">{new Date(msg.timestamp).toLocaleTimeString()}</span>
40 </div>
41 ))}
42 </div>
43
44 <form onSubmit={handleSend}>
45 <input
46 type="text"
47 value={message}
48 onChange={(e) => setMessage(e.target.value)}
49 placeholder="Type a message..."
50 disabled={!isOpen}
51 />
52 <button type="submit" disabled={!isOpen}>Send</button>
53 </form>
54 </div>
55 );
56}
Key Features:
- 🔄 Automatic reconnection with configurable attempts
- 📊 Connection state tracking
- 🛡️ Error handling
- 📨 Easy message sending with JSON support
- 🧩 Customizable event handlers
8. useOnClickOutside
Detect clicks outside an element — commonly used for closing modals or dropdowns.
1// hooks/useOnClickOutside.js
2import { useEffect } from 'react';
3
4function useOnClickOutside(ref, handler) {
5 useEffect(() => {
6 const listener = event => {
7 // Do nothing if clicking ref's element or descendent elements
8 if (!ref.current || ref.current.contains(event.target)) return;
9 handler(event);
10 };
11
12 document.addEventListener('mousedown', listener);
13 document.addEventListener('touchstart', listener);
14
15 return () => {
16 document.removeEventListener('mousedown', listener);
17 document.removeEventListener('touchstart', listener);
18 };
19 }, [ref, handler]);
20}
21
22export default useOnClickOutside;
9. useHover
Track hover state for a DOM element.
1// hooks/useHover.js
2import { useState, useRef, useEffect } from 'react';
3
4function useHover() {
5 const [hovered, setHovered] = useState(false);
6 const ref = useRef(null);
7
8 useEffect(() => {
9 const node = ref.current;
10 if (!node) return;
11
12 const handleMouseOver = () => setHovered(true);
13 const handleMouseOut = () => setHovered(false);
14
15 node.addEventListener('mouseover', handleMouseOver);
16 node.addEventListener('mouseout', handleMouseOut);
17
18 return () => {
19 node.removeEventListener('mouseover', handleMouseOver);
20 node.removeEventListener('mouseout', handleMouseOut);
21 };
22 }, []);
23
24 return [ref, hovered];
25}
26
27export default useHover;
10. useMediaQuery
Use media queries in JS to react to device or screen changes.
1// hooks/useMediaQuery.js
2import { useState, useEffect } from 'react';
3
4function useMediaQuery(query) {
5 const [matches, setMatches] = useState(false);
6
7 useEffect(() => {
8 // Create a media query list
9 const media = window.matchMedia(query);
10 // Set initial state
11 setMatches(media.matches);
12
13 // Define and add event listener
14 const handler = (e) => setMatches(e.matches);
15 media.addEventListener('change', handler);
16
17 // Clean up
18 return () => media.removeEventListener('change', handler);
19 }, [query]);
20
21 return matches;
22}
23
24export default useMediaQuery;
Usage Example:
1// Using media queries for responsive design
2const isMobile = useMediaQuery('(max-width: 768px)');
3
4return (
5 <div className={isMobile ? 'mobile-layout' : 'desktop-layout'}>
6 {isMobile ? <MobileMenu /> : <DesktopMenu />}
7 </div>
8);
Conclusion
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! 🚀