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.
Top comments (0)