Top 10 Custom Hooks for Every React Developer

Top 10 Custom Hooks for Every React Developer

reactreact tipsfrontendcutom hooks
ali durul

ali durul
@lee_dev

Published on Friday, May 2, 2025


3

0

440

React’s custom hooks allow developers to reuse logic across components, making code more maintainable, modular, and easier to read. Here’s a rundown of the ten useful custom hooks, each with a practical use case and example.

                                                                                         .  .  .

useFetch

The useFetch hook is commonly used to fetch data from an API. It manages loading, error, and response states, encapsulating all the logic needed for data retrieval.

import { useState, useEffect } from 'react';

function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await fetch(url);
        const result = await response.json();
        setData(result);
      } catch (err) {
        setError(err);
      } finally {
        setLoading(false);
      }
    };
    fetchData();
  }, [url]);

  return { data, loading, error };
}

Usage: const { data, loading, error } = useFetch('https://api.example.com/data');

                                                                                         .  .  .

usePrevious

usePrevious saves the previous value of a state or prop, which can be handy for animations, form data, or complex state management.

import { useRef, useEffect } from 'react';

function usePrevious(value) {
  const ref = useRef();

  useEffect(() => {
    ref.current = value;
  }, [value]);

  return ref.current;
}

Usage: const previousCount = usePrevious(count);

                                                                                         .  .  .

useToggle

A straightforward custom hook, useToggle manages boolean states efficiently, useful for toggling themes, modals, and UI elements.

import { useState } from 'react';

function useToggle(initialValue = false) {
  const [value, setValue] = useState(initialValue);

  const toggle = () => setValue(prev => !prev);

  return [value, toggle];
}

Usage: const [isToggled, toggle] = useToggle();

                                                                                         .  .  .

useLocalStorage

useLocalStorage helps in setting, getting, and updating values stored in the browser’s local storage, enabling persistent data storage.

import { useState } from 'react';

function useLocalStorage(key, initialValue) {
  const [storedValue, setStoredValue] = useState(() => {
    try {
      const item = localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      console.log(error);
      return initialValue;
    }
  });

  const setValue = value => {
    try {
      setStoredValue(value);
      localStorage.setItem(key, JSON.stringify(value));
    } catch (error) {
      console.log(error);
    }
  };

  return [storedValue, setValue];
}

Usage: const [name, setName] = useLocalStorage('name', 'Guest');

                                                                                         .  .  .

useDebounce

Debouncing prevents rapid triggering of events. useDebounce can be useful for input fields or search bars.

import { useState, useEffect } from 'react';

function useDebounce(value, delay) {
  const [debouncedValue, setDebouncedValue] = useState(value);

  useEffect(() => {
    const handler = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);

    return () => clearTimeout(handler);
  }, [value, delay]);

  return debouncedValue;
}

Usage: const debouncedSearchTerm = useDebounce(searchTerm, 500);

                                                                                         .  .  .

useInterval

useInterval manages recurring actions by setting up intervals in a way that integrates well with React's lifecycle.

import { useEffect, useRef } from 'react';

function useInterval(callback, delay) {
  const savedCallback = useRef();

  useEffect(() => {
    savedCallback.current = callback;
  }, [callback]);

  useEffect(() => {
    if (delay !== null) {
      const id = setInterval(() => savedCallback.current(), delay);
      return () => clearInterval(id);
    }
  }, [delay]);
}

Usage:  useInterval(() => { setCount(prevCount => prevCount + 1)}, 1000);

                                                                                         .  .  .

useWindowSize

useWindowSize is helpful for responsive design, allowing your component to adapt based on the viewport size.

import { useState, useEffect } from 'react';

function useWindowSize() {
  const [windowSize, setWindowSize] = useState({
    width: window.innerWidth,
    height: window.innerHeight,
  });

  useEffect(() => {
    const handleResize = () => {
      setWindowSize({
        width: window.innerWidth,
        height: window.innerHeight,
      });
    };
    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize);
  }, []);

  return windowSize;
}

Usage: const { width, height } = useWindowSize();

                                                                                         .  .  .

useOnClickOutside

This hook is useful for closing modals, dropdowns, or any component that should close when clicking outside of it.

import { useEffect } from 'react';

function useOnClickOutside(ref, handler) {
  useEffect(() => {
    const listener = event => {
      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]);
}

Usage: 

const modalRef = useRef();
useOnClickOutside(modalRef, () => setIsOpen(false));

                                                                                         .  .  .

useHover

Detecting hover over an element can be useful for tooltips, animations, or any UI feedback based on hover state.

import { useState, useRef, useEffect } from 'react';

function useHover() {
  const [hovered, setHovered] = useState(false);
  const ref = useRef(null);

  useEffect(() => {
    const handleMouseOver = () => setHovered(true);
    const handleMouseOut = () => setHovered(false);
    const node = ref.current;
    if (node) {
      node.addEventListener('mouseover', handleMouseOver);
      node.addEventListener('mouseout', handleMouseOut);
    }
    return () => {
      if (node) {
        node.removeEventListener('mouseover', handleMouseOver);
        node.removeEventListener('mouseout', handleMouseOut);
      }
    };
  }, [ref]);

  return [ref, hovered];
}

Usage: const [hoverRef, isHovered] = useHover();

                                                                                         .  .  .

useMediaQuery

useMediaQuery listens to CSS media queries, allowing you to apply specific styles or behaviors based on device size.

import { useEffect, useState } from 'react';

function useMediaQuery(query) {
  const [matches, setMatches] = useState(false);

  useEffect(() => {
    const mediaQuery = window.matchMedia(query);
    setMatches(mediaQuery.matches);

    const handler = event => setMatches(event.matches);
    mediaQuery.addEventListener('change', handler);

    return () => mediaQuery.removeEventListener('change', handler);
  }, [query]);

  return matches;
}

Usage: const isLargeScreen = useMediaQuery('(min-width: 1024px)');

                                                                                         .  .  .

useWebSocket

The useWebSocket hook connects your app to a WebSocket server, making it easy to send and receive real-time data. It handles reconnections, message buffering, and event-based handling, making it ideal for applications like chat apps, live notifications, or real-time data updates.

This example demonstrates a reusable WebSocket hook with features for connection management, event handling, and graceful cleanup.

import { useState, useEffect, useRef, useCallback } from 'react';

function useWebSocket(url, options = {}) {
  const { reconnect = true, reconnectInterval = 5000, onOpen, onMessage, onError, onClose } = options;
  const [isConnected, setIsConnected] = useState(false);
  const [lastMessage, setLastMessage] = useState(null);
  const websocketRef = useRef(null);
  const reconnectTimeout = useRef(null);

  const connect = useCallback(() => {
    websocketRef.current = new WebSocket(url);

    websocketRef.current.onopen = (event) => {
      setIsConnected(true);
      onOpen && onOpen(event);
    };

    websocketRef.current.onmessage = (event) => {
      setLastMessage(event.data);
      onMessage && onMessage(event);
    };

    websocketRef.current.onerror = (event) => {
      onError && onError(event);
    };

    websocketRef.current.onclose = (event) => {
      setIsConnected(false);
      onClose && onClose(event);
      if (reconnect) {
        reconnectTimeout.current = setTimeout(connect, reconnectInterval);
      }
    };
  }, [url, reconnect, reconnectInterval, onOpen, onMessage, onError, onClose]);

  const sendMessage = useCallback((message) => {
    if (isConnected && websocketRef.current) {
      websocketRef.current.send(message);
    }
  }, [isConnected]);

  useEffect(() => {
    connect();

    return () => {
      if (websocketRef.current) {
        websocketRef.current.close();
      }
      clearTimeout(reconnectTimeout.current);
    };
  }, [connect]);

  return { isConnected, sendMessage, lastMessage };
}

Features of useWebSocket

  1. Reconnection Handling: If the connection closes, the hook automatically tries to reconnect after a specified interval.
  2. Event Handling: Accepts callbacks for onOpenonMessageonError, and onClose events to handle them as needed.
  3. Message Sending: Provides a sendMessage function that only sends messages when the WebSocket is open.
  4. Last Message Storage: Stores the latest message received, allowing easy access to the most recent data without re-subscribing.

Usage Example

Here’s how to use useWebSocket in a component, such as a live chat or a real-time notification system.

function ChatApp() {
  const { isConnected, sendMessage, lastMessage } = useWebSocket('ws://localhost:4000/chat', {
    reconnect: true,
    reconnectInterval: 3000,
    onOpen: () => console.log('Connected to WebSocket'),
    onMessage: (event) => console.log('New message received:', event.data),
    onClose: () => console.log('Disconnected from WebSocket'),
  });

  const [inputValue, setInputValue] = useState('');

  const handleSend = () => {
    sendMessage(inputValue);
    setInputValue('');
  };

  return (
    <div>
      <h3>WebSocket Chat</h3>
      <div>
        <p>{isConnected ? 'Connected' : 'Disconnected'}</p>
        <input
          type="text"
          value={inputValue}
          onChange={(e) => setInputValue(e.target.value)}
          placeholder="Type your message..."
        />
        <button onClick={handleSend} disabled={!isConnected}>
          Send
        </button>
      </div>
      <div>
        <h4>Last Message:</h4>
        <p>{lastMessage}</p>
      </div>
    </div>
  );
}

How It Works

  1. Connection Management: The hook automatically manages the WebSocket connection and allows reconnecting after disconnection.
  2. Sending Messages: You can send messages through the sendMessage function, which only sends when the WebSocket is open.
  3. Event Handlers: Custom event handlers (like onMessage) let you react to WebSocket events without polluting your component code.
  4. Last Message: You can access the last received message directly, simplifying data access in the UI.