Custom Hooks in React: Creating Reusable Logic with Examples
React’s custom hooks are a powerful feature that allows developers to encapsulate and reuse logic across components. By creating custom hooks, you can avoid code duplication, improve readability, and make your components more focused on rendering rather than logic. In this article, we’ll explore how to create custom hooks, with practical examples like useFetch
, useLocalStorage
, and useAuth
. We’ll also discuss best practices and provide useful resources for further learning.
1. What Are Custom Hooks?
Custom hooks are JavaScript functions that start with the prefix use
and can call other hooks (e.g., useState
, useEffect
). They allow you to extract component logic into reusable functions. Custom hooks follow the same rules as built-in hooks:
- They must start with the word
use
. - They can only be called at the top level of a functional component or another hook.
2. Why Use Custom Hooks?
Custom hooks offer several benefits:
- Reusability: Share logic across multiple components.
- Separation of Concerns: Keep components focused on rendering.
- Readability: Simplify complex components by moving logic into hooks.
- Testability: Isolate logic for easier testing.
3. Examples of Custom Hooks
1. useFetch
: Fetching Data
A common use case is fetching data from an API. Instead of writing the same logic in multiple components, you can create a useFetch
hook.
Implementation
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | import { useState, useEffect } from 'react' ; function useFetch(url) { const [data, setData] = useState( null ); const [loading, setLoading] = useState( true ); const [error, setError] = useState( null ); useEffect(() => { const fetchData = async () => { try { const response = await fetch(url); if (!response.ok) throw new Error( 'Network response was not ok' ); const result = await response.json(); setData(result); } catch (error) { setError(error); } finally { setLoading( false ); } }; fetchData(); }, [url]); return { data, loading, error }; } |
Usage
01 02 03 04 05 06 07 08 09 10 11 12 13 14 | function App() { if (loading) return <div>Loading...</div>; if (error) return <div>Error: {error.message}</div>; return ( <ul> {data?.map((post) => ( <li key={post.id}>{post.title}</li> ))} </ul> ); } |
2. useLocalStorage
: Persisting Data
Another common use case is persisting data in the browser’s local storage. The useLocalStorage
hook simplifies this process.
Implementation
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | import { useState } from 'react' ; function useLocalStorage(key, initialValue) { const [storedValue, setStoredValue] = useState(() => { try { const item = window.localStorage.getItem(key); return item ? JSON.parse(item) : initialValue; } catch (error) { console.error(error); return initialValue; } }); const setValue = (value) => { try { const valueToStore = value instanceof Function ? value(storedValue) : value; setStoredValue(valueToStore); window.localStorage.setItem(key, JSON.stringify(valueToStore)); } catch (error) { console.error(error); } }; return [storedValue, setValue]; } |
Usage
01 02 03 04 05 06 07 08 09 10 11 12 13 14 | function App() { const [name, setName] = useLocalStorage( 'name' , 'John Doe' ); return ( <div> <input type= "text" value={name} onChange={(e) => setName(e.target.value)} /> <p>Hello, {name}!</p> </div> ); } |
3. useAuth
: Managing Authentication
Managing authentication state is a common requirement in applications. The useAuth
hook can handle login, logout, and user state.
Implementation
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 | import { useState } from 'react' ; function useAuth() { const [user, setUser] = useState( null ); const login = (userData) => { setUser(userData); localStorage.setItem( 'user' , JSON.stringify(userData)); }; const logout = () => { setUser( null ); localStorage.removeItem( 'user' ); }; return { user, login, logout }; } |
Usage
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 | function App() { const { user, login, logout } = useAuth(); return ( <div> {user ? ( <div> <p>Welcome, {user.name}!</p> <button onClick={logout}>Logout</button> </div> ) : ( <button onClick={() => login({ name: 'John Doe' })}>Login</button> )} </div> ); } |
4. Best Practices for Custom Hooks
Custom hooks in React are a powerful way to encapsulate and reuse logic across components. They help keep your code DRY (Don’t Repeat Yourself) and make your components more focused and readable. However, to ensure your custom hooks are effective and maintainable, it’s important to follow best practices. Below, we’ll outline these best practices in a table format for easy reference, along with necessary implementations.
Here’s a table summarizing the best practices for creating and using custom hooks:
Best Practice | Description | Example |
---|---|---|
Prefix with use | Always start the hook name with use to follow React’s naming conventions. | useFetch , useLocalStorage , useAuth |
Single Responsibility | Each hook should handle one specific piece of logic. | useFetch for data fetching, useLocalStorage for local storage management. |
Document the Hook | Add comments or documentation to explain the purpose and usage of the hook. | Include a description of inputs, outputs, and side effects. |
Test Custom Hooks | Test hooks in isolation using libraries like React Testing Library. | Write unit tests for useFetch to ensure it handles loading and errors. |
Avoid Overuse | Only create custom hooks when the logic is reusable across multiple components. | Avoid creating hooks for one-off logic. |
Handle Edge Cases | Ensure your hook handles edge cases like errors, null values, or missing data. | In useFetch , handle network errors and invalid responses. |
Keep Hooks Small | Break down complex logic into smaller, reusable hooks. | Split useAuth into useLogin and useLogout if needed. |
Use Dependency Arrays | Properly use dependency arrays in useEffect to avoid unnecessary re-renders. | In useFetch , include url in the dependency array. |
5. Useful Resources
- React Documentation: Custom Hooks
- useHooks: A collection of reusable custom hooks (useHooks.com)
- React Testing Library: Testing Custom Hooks
- Baeldung Tutorials: Creating Custom Hooks
- GitHub Repositories: Explore open-source custom hooks on GitHub for inspiration.
By leveraging custom hooks, you can write cleaner, more maintainable React code. Whether you’re fetching data, managing local storage, or handling authentication, custom hooks provide a flexible and reusable solution. Start creating your own hooks today and take your React development to the next level!