React hooks revolutionized how we write components. But the true power of this system unfolds when you move beyond the built-in hooks and start creating your own. Custom hooks are the most important pattern for sharing stateful logic between components, and mastering them is a key step in becoming a proficient React developer.
This guide covers how to build them, when they are necessary, and—just as importantly—when to avoid them, with advanced tips integrated throughout.
What is a Custom Hook?
A custom hook is simply a JavaScript function whose name starts with use and that calls other hooks. That's it. It's a convention that allows you to extract component logic into a reusable function.
How to Create a Custom Hook
Let's start by identifying a piece of logic we might want to reuse. A common scenario is managing a boolean toggle state.
Before (In a Component):
function MyComponent() {
const [isOpen, setIsOpen] = useState(false);
const toggle = () => setIsOpen(!isOpen);
return (
<div>
<button onClick={toggle}>{isOpen ? 'Close' : 'Open'}</button>
{isOpen && <p>Content is visible!</p>}
</div>
);
}
This is fine, but if you need this toggle logic in many components, repeating it is tedious. Let's extract it into a useToggle hook.
After (Creating the useToggle Hook):
import { useState, useCallback } from 'react';
export function useToggle(initialState = false) {
const [state, setState] = useState(initialState);
// Senior-level tip: Always wrap functions returned from hooks in `useCallback`.
// This ensures the function reference is stable and prevents unnecessary
// re-renders in child components that might receive it as a prop.
const toggle = useCallback(() => setState(prevState => !prevState), []);
return [state, toggle];
}
Using the Custom Hook:
import { useToggle } from './useToggle';
function MyComponent() {
const [isOpen, toggleIsOpen] = useToggle(false);
return (
<div>
<button onClick={toggleIsOpen}>{isOpen ? 'Close' : 'Open'}</button>
{isOpen && <p>Content is visible!</p>}
</div>
);
}
We've successfully extracted the stateful logic into a clean, reusable, and testable function.
Advanced Strategy: Return an Array or an Object?
- Return an array (like
[value, updateFunction]) when the hook has one primary value and its updater. This mimicsuseStateand allows the consumer to name the returned values freely.- Return an object (like
{ data, isLoading, error }) when the hook returns multiple, distinct values. This is more readable as the consumer knows exactly what they are getting (e.g.,const { data } = useFetch(...)).
When to Create a Custom Hook
Knowing when to abstract logic into a hook is a skill.
-
When Logic is Repeated (DRY Principle): The most obvious sign. If you find yourself copying and pasting the same block of
useStateanduseEffectcalls across multiple components, it's time for a hook. A classic example is auseFetchhook for data fetching.
function useFetch(url) { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { // ... fetch logic using the url ... }, [url]); // Re-runs when URL changes return { data, loading, error }; } To Simplify Complex Components: Sometimes a single component becomes bloated with complex state interactions, timers, or event listeners. Abstracting this logic into one or more custom hooks can make the component itself much cleaner and focused only on rendering the UI.
-
To Encapsulate Context (The
useAuthPattern): A powerful senior-level pattern is to couple a Context with a custom hook. Instead of forcing consumers to importuseContextandAuthContexteverywhere, you provide a simpleuseAuthhook.
// in auth-context.js const AuthContext = createContext(null); // ... AuthProvider component ... // The custom hook that consumers will use export function useAuth() { const context = useContext(AuthContext); if (context === null) { throw new Error('useAuth must be used within an AuthProvider'); } return context; } // In a component: // const { user, login } = useAuth(); // Clean and simple!
When NOT to Create a Custom Hook
Abstraction has a cost. Avoid these anti-patterns.
Premature Abstraction: Don't create a hook for something you've only written once. Wait until you need the same logic at least a second time. Creating abstractions too early often leads to overly complex code that is harder to change.
Wrapping a Single, Simple Hook: If your custom hook only contains a single
useStatecall and no other logic, it's a needless layer of indirection.const [value, setValue] = useState()is already as simple as it gets.For Sharing UI: This is the most important rule. Hooks are for logic, components are for UI. If you want to share JSX, create a component, not a hook. A hook should never return JSX.
Conclusion
Custom hooks are more than just a clever feature; they are a fundamental part of the React design philosophy. They encourage composition over inheritance and allow you to build a library of reusable, stateful logic that is specific to your application. By learning to identify the right moments for abstraction and applying advanced patterns like memoizing callbacks and encapsulating context, you can write React applications that are cleaner, more maintainable, and easier to scale.
Top comments (0)