DEV Community

SOVANNARO
SOVANNARO

Posted on

7 React Custom Hooks I Can’t Live Without in My Projects 🚀

React Hooks have revolutionized the way we write components in React. They allow us to use state, side effects, and other React features without writing class components. But beyond the built-in hooks like useState, useEffect, and useContext, custom hooks take things to the next level. Custom hooks enable us to encapsulate reusable logic, making our code cleaner, more modular, and easier to maintain.

In this article, I’ll share 7 custom hooks that I can’t live without in my React projects. These hooks have saved me countless hours, reduced boilerplate code, and made my applications more efficient. Whether you’re a beginner or an experienced React developer, these hooks will elevate your projects to the next level. Let’s dive in! 🌊


Why Use Custom Hooks?

Before we jump into the list, let’s talk about why custom hooks are so powerful:

  1. Reusability: Custom hooks allow you to extract and reuse logic across multiple components.
  2. Readability: By moving complex logic into custom hooks, your components become cleaner and easier to understand.
  3. Testability: Custom hooks can be tested independently, making your codebase more robust.
  4. Separation of Concerns: Hooks help you separate business logic from UI logic, leading to better architecture.

Now, let’s explore the 7 custom hooks that have become indispensable in my React projects! 🛠️


1. useFetch 🎣

Fetching data is a common task in React applications. Instead of writing the same fetch logic in every component, you can create a useFetch hook to handle data fetching, loading states, and errors.

Implementation:

import { useState, useEffect } from 'react';

const 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);
        if (!response.ok) throw new Error('Network response was not ok');
        const result = await response.json();
        setData(result);
      } catch (error) {
        setError(error);
      } finally {
        setLoading(false);
      }
    };

    fetchData();
  }, [url]);

  return { data, loading, error };
};

export default useFetch;
Enter fullscreen mode Exit fullscreen mode

Usage:

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

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;

  return (
    <div>
      <h1>Data:</h1>
      <pre>{JSON.stringify(data, null, 2)}</pre>
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

Why It’s Indispensable: It simplifies data fetching and reduces repetitive code across components.


2. useLocalStorage 💾

Persisting data in the browser’s local storage is a common requirement. The useLocalStorage hook makes it easy to sync state with local storage.

Implementation:

import { useState } from 'react';

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

  const setValue = (value) => {
    try {
      const valueToStore = value instanceof Function ? value(storedValue) : value;
      setStoredValue(valueToStore);
      window.localStorage.setItem(key, JSON.stringify(valueToStore));
    } catch (error) {
      console.error(error);
    }
  };

  return [storedValue, setValue];
};

export default useLocalStorage;
Enter fullscreen mode Exit fullscreen mode

Usage:

const MyComponent = () => {
  const [name, setName] = useLocalStorage('name', 'John Doe');

  return (
    <div>
      <input
        type="text"
        value={name}
        onChange={(e) => setName(e.target.value)}
      />
      <p>Hello, {name}!</p>
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

Why It’s Indispensable: It seamlessly syncs state with local storage, making it perfect for persisting user preferences.


3. useDarkMode 🌙

Dark mode is a popular feature in modern applications. The useDarkMode hook simplifies toggling between light and dark themes.

Implementation:

import { useEffect, useState } from 'react';

const useDarkMode = () => {
  const [isDarkMode, setIsDarkMode] = useState(() => {
    return localStorage.theme === 'dark' || (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches);
  });

  useEffect(() => {
    if (isDarkMode) {
      document.documentElement.classList.add('dark');
      localStorage.theme = 'dark';
    } else {
      document.documentElement.classList.remove('dark');
      localStorage.theme = 'light';
    }
  }, [isDarkMode]);

  const toggleDarkMode = () => setIsDarkMode((prev) => !prev);

  return { isDarkMode, toggleDarkMode };
};

export default useDarkMode;
Enter fullscreen mode Exit fullscreen mode

Usage:

const MyComponent = () => {
  const { isDarkMode, toggleDarkMode } = useDarkMode();

  return (
    <div>
      <button onClick={toggleDarkMode}>
        {isDarkMode ? 'Light Mode' : 'Dark Mode'}
      </button>
      <p>Current mode: {isDarkMode ? 'Dark' : 'Light'}</p>
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

Why It’s Indispensable: It makes implementing dark mode a breeze, improving user experience.


4. useWindowSize 🖥️

Knowing the size of the browser window is useful for responsive design. The useWindowSize hook provides real-time updates of the window dimensions.

Implementation:

import { useState, useEffect } from 'react';

const 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;
};

export default useWindowSize;
Enter fullscreen mode Exit fullscreen mode

Usage:

const MyComponent = () => {
  const { width, height } = useWindowSize();

  return (
    <div>
      <p>Window Width: {width}px</p>
      <p>Window Height: {height}px</p>
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

Why It’s Indispensable: It simplifies responsive design by providing real-time window dimensions.


5. useDebounce ⏳

Debouncing is essential for optimizing performance, especially when handling user input. The useDebounce hook delays the execution of a function until a specified time has passed.

Implementation:

import { useEffect, useState } from 'react';

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

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

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

  return debouncedValue;
};

export default useDebounce;
Enter fullscreen mode Exit fullscreen mode

Usage:

const MyComponent = () => {
  const [searchTerm, setSearchTerm] = useState('');
  const debouncedSearchTerm = useDebounce(searchTerm, 500);

  useEffect(() => {
    // Perform search API call with debouncedSearchTerm
    console.log('Searching for:', debouncedSearchTerm);
  }, [debouncedSearchTerm]);

  return (
    <input
      type="text"
      value={searchTerm}
      onChange={(e) => setSearchTerm(e.target.value)}
      placeholder="Search..."
    />
  );
};
Enter fullscreen mode Exit fullscreen mode

Why It’s Indispensable: It prevents unnecessary API calls or expensive computations, improving performance.


6. useClickOutside 🖱️

Detecting clicks outside of a specific element is useful for closing dropdowns, modals, or menus. The useClickOutside hook simplifies this functionality.

Implementation:

import { useEffect } from 'react';

const useClickOutside = (ref, callback) => {
  useEffect(() => {
    const handleClickOutside = (event) => {
      if (ref.current && !ref.current.contains(event.target)) {
        callback();
      }
    };

    document.addEventListener('mousedown', handleClickOutside);
    return () => document.removeEventListener('mousedown', handleClickOutside);
  }, [ref, callback]);
};

export default useClickOutside;
Enter fullscreen mode Exit fullscreen mode

Usage:

const MyComponent = () => {
  const dropdownRef = useRef(null);
  const [isOpen, setIsOpen] = useState(false);

  useClickOutside(dropdownRef, () => setIsOpen(false));

  return (
    <div ref={dropdownRef}>
      <button onClick={() => setIsOpen(!isOpen)}>Toggle Dropdown</button>
      {isOpen && <div>Dropdown Content</div>}
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

Why It’s Indispensable: It simplifies handling click-outside events, improving UX for dropdowns and modals.


7. usePrevious 🔄

Sometimes, you need to know the previous value of a state or prop. The usePrevious hook makes this easy.

Implementation:

import { useRef, useEffect } from 'react';

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

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

  return ref.current;
};

export default usePrevious;
Enter fullscreen mode Exit fullscreen mode

Usage:

const MyComponent = ({ count }) => {
  const prevCount = usePrevious(count);

  return (
    <div>
      <p>Current Count: {count}</p>
      <p>Previous Count: {prevCount}</p>
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

Why It’s Indispensable: It provides a simple way to track previous values, which is useful for comparisons and debugging.


Final Thoughts 🎉

Custom hooks are a game-changer in React development. They allow you to encapsulate logic, reduce boilerplate code, and create more maintainable applications. The 7 custom hooks I’ve shared in this article—useFetch, useLocalStorage, useDarkMode, useWindowSize, useDebounce, useClickOutside, and usePrevious—have become essential tools in my React toolkit.

I encourage you to try these hooks in your projects and see how they can improve your workflow. And don’t stop here—experiment with creating your own custom hooks to solve unique challenges in your applications. Happy coding! 🚀


What’s your favorite custom hook? Do you have any custom hooks that you can’t live without? Share your thoughts and experiences in the comments below!

Top comments (9)

Collapse
 
dzungnt98 profile image
Dzung Nguyen

Nice share! For those who work with React and hooks for a long time, there are some available libraries that you can leverage to save time.

dev.to/dzungnt98/simplify-your-rea...

Collapse
 
jean_nascimento_92cbab15c profile image
Jean Nascimento

A little correction in useFetch:

import { useState, useEffect } from 'react';

const useFetch = (url: string) => {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await fetch(url);
        if (!response.ok) throw new Error('Network response was not ok');
        const result = await response.json();
        setData(result);
      } catch (error) {
        if (error instanceof Error) {
          setError(error.message);
        }
      } finally {
        setLoading(false);
      }
    };

    fetchData();
  }, [url]);

  return { data, loading, error };
};

export default useFetch;
Enter fullscreen mode Exit fullscreen mode
Collapse
 
jaykgupta profile image
Jay Kumar Gupta

nice share!

Collapse
 
pravinjadhav profile image
Pravin Jadhav
Collapse
 
wasim_khan_dfe4f6dc379374 profile image
Wasim Khan

Awesome posting...

Collapse
 
sachin_maurya_4aa454540c6 profile image
Sachin Maurya
Collapse
 
savvy-itch profile image
Michael Savych • Edited

Did OP plagiarize your article?

Collapse
 
mohammad_faramarzi profile image
Mohammad Faramarzi

Great 👍

Collapse
 
zarkas profile image
Michael Nielsen

Thanks for sharing