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.