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:
- Reusability: Custom hooks allow you to extract and reuse logic across multiple components.
- Readability: By moving complex logic into custom hooks, your components become cleaner and easier to understand.
- Testability: Custom hooks can be tested independently, making your codebase more robust.
- 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;
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>
);
};
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;
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>
);
};
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;
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>
);
};
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;
Usage:
const MyComponent = () => {
const { width, height } = useWindowSize();
return (
<div>
<p>Window Width: {width}px</p>
<p>Window Height: {height}px</p>
</div>
);
};
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;
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..."
/>
);
};
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;
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>
);
};
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;
Usage:
const MyComponent = ({ count }) => {
const prevCount = usePrevious(count);
return (
<div>
<p>Current Count: {count}</p>
<p>Previous Count: {prevCount}</p>
</div>
);
};
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)
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...
A little correction in useFetch:
nice share!
dev.to/pravinjadhav/svelte-5-sorry...
Awesome posting...
Real article llink
medium.com/@letscodefuture/7-react...
Did OP plagiarize your article?
Great 👍
Thanks for sharing