Custom hooks are not just a convenience in React—they're a game-changer for modular and maintainable code. They allow developers to encapsulate logic, manage state, and streamline complex functionalities in ways that weren’t possible before.
React’s powerful functional programming paradigm has redefined modern front-end development, paving the way for modular, maintainable, and reusable code. Among its many capabilities, custom hooks stand out as a key enabler for building smarter, cleaner components. Today, let’s dive into some essential custom hooks that every developer should have in their toolkit and learn how to implement them effectively.
- useFetch: Simplify API Calls 🌐 Fetching data is a common task in React. The useFetch hook abstracts repetitive logic, streamlining API calls and managing state elegantly.
Implementation:
import { useState, useEffect } from "react";
function useFetch<T>(url: string) {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
const fetchData = async () => {
setLoading(true);
try {
const response = await fetch(url);
const result = await response.json();
setData(result);
} catch (err) {
setError(err as Error);
} finally {
setLoading(false);
}
};
fetchData();
}, [url]);
return { data, loading, error };
}
export default useFetch;
Usage:
const { data, loading, error } = useFetch<User[]>('/api/users');
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return <ul>{data?.map(user => <li key={user.id}>{user.name}</li>)}</ul>;
- useDebounce: Optimize Performance ⏳ Handling frequent user input, such as search or form fields, is made efficient with a debounce hook, reducing unnecessary renders and API calls.
Implementation:
import { useState, useEffect } from "react";
function useDebounce<T>(value: T, delay: number): T {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const handler = setTimeout(() => setDebouncedValue(value), delay);
return () => clearTimeout(handler);
}, [value, delay]);
return debouncedValue;
}
export default useDebounce;
Usage:
const [searchTerm, setSearchTerm] = useState('');
const debouncedSearch = useDebounce(searchTerm, 500);
useEffect(() => {
if (debouncedSearch) {
// Trigger API or other actions
}
}, [debouncedSearch]);
- useToggle: Manage Boolean States Easily "Managing toggle states for modals, dropdowns, or theme switches is effortless with a custom useToggle hook, keeping your code clean and reusable.
Implementation:
import { useState } from "react";
function useToggle(initialState = false) {
const [state, setState] = useState(initialState);
const toggle = () => setState(prev => !prev);
return [state, toggle] as const;
}
export default useToggle;
Usage:
const [isModalOpen, toggleModal] = useToggle();
return (
<div>
<button onClick={toggleModal}>Toggle Modal</button>
{isModalOpen && <p>Modal Content</p>}
</div>
);
- useLocalStorage: Persist Data Locally 📂 Storing and retrieving data from localStorage becomes seamless and reusable with a custom useLocalStorage hook.
Implementation:
import { useState } from "react";
function useLocalStorage<T>(key: string, initialValue: T) {
const [storedValue, setStoredValue] = useState<T>(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
console.error(error);
return initialValue;
}
});
const setValue = (value: T) => {
try {
setStoredValue(value);
window.localStorage.setItem(key, JSON.stringify(value));
} catch (error) {
console.error(error);
}
};
return [storedValue, setValue] as const;
}
export default useLocalStorage;
Usage:
const [theme, setTheme] = useLocalStorage('theme', 'light');
return (
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
Toggle Theme
</button>
);
- usePrevious: Track Previous State 📜 Tracking a value's previous state is essential for comparisons and animations, easily achieved with a custom usePrevious hook.
Implementation:
import { useEffect, useRef } from "react";
function usePrevious<T>(value: T): T | undefined {
const ref = useRef<T>();
useEffect(() => {
ref.current = value;
}, [value]);
return ref.current;
}
export default usePrevious;
Usage:
const [count, setCount] = useState(0);
const prevCount = usePrevious(count);
return (
<p>
Now: {count}, Before: {prevCount}
</p>
);
- useClickOutside: Detect Outside Clicks 🖱️ Perfect for closing modals or dropdowns when clicking outside, using a custom useClickOutside hook for better user experience.
Implementation:
import { useEffect, useRef } from "react";
function useClickOutside(handler: () => void) {
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (ref.current && !ref.current.contains(event.target as Node)) {
handler();
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
}, [handler]);
return ref;
}
export default useClickOutside;
Usage:
const ref = useClickOutside(() => setDropdownOpen(false));
return (
<div ref={ref}>
{dropdownOpen && <p>Dropdown Content</p>}
</div>
);
- useMediaQuery: Handle Responsive Design 📱 Managing media queries in React is simplified with a custom useMediaQuery hook, making responsive design more efficient.
Implementation:
import { useState, useEffect } from "react";
function useMediaQuery(query: string): boolean {
const [matches, setMatches] = useState(false);
useEffect(() => {
const mediaQueryList = window.matchMedia(query);
const updateMatch = () => setMatches(mediaQueryList.matches);
updateMatch();
mediaQueryList.addEventListener('change', updateMatch);
return () => mediaQueryList.removeEventListener('change', updateMatch);
}, [query]);
return matches;
}
export default useMediaQuery;
Usage:
const isMobile = useMediaQuery('(max-width: 768px)');
return <p>{isMobile ? 'Mobile View' : 'Desktop View'}</p>;
_Custom hooks showcase React’s flexibility and power, enabling cleaner, reusable, and more maintainable code.
_
By leveraging custom hooks, developers can simplify complex functionality and create reusable, efficient code. The examples above demonstrate how these hooks elegantly solve common challenges.
I hope you found this helpful! I'd be happy if we connected on LinkedIn 🚀
Top comments (33)
I'd prefer to use Tanstack Query over
useFetch
if I needed to improve performance because of the in-built caching.Other than that, really useful hooks. Great read.
You're totally right! TanStack Query is great for better performance with its caching and extra features. But for simpler cases, a custom hook like useFetch works just fine. Glad you liked the hook—thanks for the support! 😊
Hello Joodi. I'm Grace
I hope this doesn't sound intrusive.
I'm a newbie React Developer and I'm totally available for Volunteer work.
I'm ready to volunteer without a pay to improve on my skills.
Looking forward to hearing from you.
Thanks for the write up.
The return values from your custom hooks will create a new object or array value on each render, which means they will fail referential equality checks. This becomes a problem if you want to use the values returned from your custom hooks in a useEffect hook for example. The useEffect would be triggered on each render, even if the data returned from your custom hooks hasn't changed.
Thank you for the valuable feedback! You’re absolutely right. Returning new objects or arrays from custom hooks can lead to unnecessary re-renders, especially when used in 𝘂𝘀𝗲𝗘𝗳𝗳𝗲𝗰𝘁 or other hooks that rely on referential equality.
I’ll update the article to include 𝘂𝘀𝗲𝗠𝗲𝗺𝗼 to prevent unnecessary reference changes. This will ensure better performance by only updating the reference when the actual data changes. Thanks again for pointing this out! 😊
Lol this reply reads like sloppy ai
Thanks for the feedback! I can assure you this was written with care—sometimes technical responses can come off as a bit formal. I appreciate your input and will keep it in mind to make my tone more natural in future replies.
These are wonderfully easy to read and simple to implement. I especially liked the useFetch hook. With returning data, loading and error it mimics Tanstack Query or Apollo Client with GraphQL but without all of the heavy lifting—great for light weight use cases..
Yeah, I found it interesting too! It's a nice lightweight solution, especially for simpler use cases. 😊
Excellent. Perhaps there is a package with all these hooks?
Thank you! Glad you liked it! 😊 There isn’t a package with all these hooks yet, but that’s a great idea—maybe it’s time to create one! 🚀
Looking forward to it.
Wonderful stuff ✨
Can't wait to begin implementing
Thank you ✨
God bless you
Thank you so much! ✨ Hope it helps you out—can’t wait to see what you create! 🙌 God bless you too! 😊
Amen
You're welcome ✨
Nice list
Thanks a lot! Happy you found it useful! 😊
Great work
Thanks a lot! Glad you liked it! 😊
I like how simple and useful your useDebounce hook! For fetch I use SWR library which is tiny and supports many user cases.
Thank you! I'm glad you liked the simplicity of the 𝘂𝘀𝗲𝗗𝗲𝗯𝗼𝘂𝗻𝗰𝗲 hook. SWR is an awesome library—lightweight and packed with features. It's a great choice for handling fetches with caching and other advanced use cases! 🚀
Very good! Thanks 👍
Appreciate it, mate! Glad you liked it! 👍🔥
Very nice
Cheers, mate! Glad you liked it! 😊
Some comments may only be visible to logged-in visitors. Sign in to view all comments.