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

npmixnpmix
16
React Hooks

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.

javascript
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:

jsx
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.

javascript
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.

javascript
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:

jsx
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.

javascript
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.

javascript
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.

javascript
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.

javascript
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:

jsx
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.

javascript
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.

javascript
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.

javascript
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:

jsx
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! 🚀

Similar articles

Never miss an update

Subscribe to receive news and special offers.

By subscribing you agree to our Privacy Policy.