Custom Hooks in React
Custom Hooks offer a powerful way to extract, share, and reuse stateful logic across multiple React components without duplicating code or restructuring your component hierarchy.
What is a Custom Hook?
A Custom Hook is simply a JavaScript function whose name starts with "use" and that may call other Hooks. It allows you to package React state and side effects into a reusable function.
The Golden Rules of Custom Hooks
- Must start with
use: This is a strict convention. Tools like the React ESLint plugin rely on this naming to enforce the Rules of Hooks (e.g., hooks cannot be called conditionally). - Can use other Hooks: Inside your custom hook, you can use
useState,useEffect,useContext, or even other custom hooks. - Hooks share stateful logic, not state itself: Each call to a hook gets a completely isolated state. If two components use the same custom hook, they do not share the data; they just share the logic that manages the data.
Example 1: useLocalStorage
Often, you want state to persist across page reloads. A useLocalStorage hook is a classic example of abstracting side-effects.
import { useState, useEffect } from 'react';
export function useLocalStorage(key, initialValue) {
// Initialize state lazily to avoid reading from localStorage on every render
const [value, setValue] = useState(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
console.error(error);
return initialValue;
}
});
// Sync state to localStorage whenever it changes
useEffect(() => {
try {
window.localStorage.setItem(key, JSON.stringify(value));
} catch (error) {
console.error(error);
}
}, [key, value]);
return [value, setValue];
}
Usage:
function App() {
const [name, setName] = useLocalStorage('username', 'Guest');
return (
<input
value={name}
onChange={(e) => setName(e.target.value)}
/>
);
}
Example 2: useFetch
Data fetching is a very common requirement. A useFetch custom hook can encapsulate loading states, error handling, and the fetch logic itself.
import { useState, useEffect } from 'react';
export function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
// Flag to prevent state updates if the component unmounts
let isMounted = true;
const fetchData = async () => {
setLoading(true);
try {
const response = await fetch(url);
if (!response.ok) throw new Error('Network response was not ok');
const json = await response.json();
if (isMounted) {
setData(json);
setError(null);
}
} catch (err) {
if (isMounted) setError(err.message);
} finally {
if (isMounted) setLoading(false);
}
};
fetchData();
return () => {
isMounted = false; // Cleanup function
};
}, [url]);
return { data, loading, error };
}
Usage:
function UsersList() {
const { data: users, loading, error } = useFetch('https://jsonplaceholder.typicode.com/users');
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error}</p>;
return (
<ul>
{users.map(user => <li key={user.id}>{user.name}</li>)}
</ul>
);
}
Custom hooks represent one of React's most elegant solutions for composing logic, making your components significantly cleaner and more focused on rendering UI.