Table of contents
Custom Hooks
1. What are Custom Hooks?
Custom hooks are essentially JavaScript functions that use other React hooks (like useState
, useEffect
, useContext
, etc.) to encapsulate reusable logic. They allow you to extract component logic into reusable functions and maintain stateful logic independently of the component’s UI.
Why use custom hooks?
To reuse logic across components without duplicating code.
To abstract complex logic into smaller, maintainable pieces.
To keep components clean and focused on rendering UI instead of handling logic.
2. Creating a Custom Hook
A custom hook is just a function that starts with the prefix use
and can call other hooks inside of it. The naming convention is important because React relies on this prefix to identify hooks and apply rules of hooks (like only calling hooks at the top level and within React components or other custom hooks).
Example: useWindowWidth
Custom Hook
import { useState, useEffect } from 'react';
function useWindowWidth() {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
const handleResize = () => setWidth(window.innerWidth);
window.addEventListener('resize', handleResize);
// Cleanup on unmount
return () => window.removeEventListener('resize', handleResize);
}, []);
return width;
}
export default useWindowWidth;
3. Using a Custom Hook
You can use a custom hook in any component just like you would use a built-in React hook.
import React from 'react';
import useWindowWidth from './useWindowWidth';
function Component() {
const width = useWindowWidth();
return (
<div>
<p>The window width is: {width}</p>
</div>
);
}
4. Benefits of Custom Hooks
Reusability: Logic can be reused across multiple components.
Separation of Concerns: Components are focused on UI, while the custom hooks handle logic.
Cleaner Components: Extracting logic into hooks keeps components simpler and more readable.
Share Stateful Logic: Custom hooks can share state and behavior between multiple components.
5. Common Use Cases for Custom Hooks
Fetching Data (with side effects)
Custom Hook Example 1 - useFetch Hook
import { useState, useEffect } from 'react'; function useFetch(url) { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { async function fetchData() { try { const response = await fetch(url); const result = await response.json(); setData(result); } catch (err) { setError(err); } finally { setLoading(false); } } fetchData(); }, [url]); return { data, loading, error }; } export default useFetch;
Usage:
const { data, loading, error } = useFetch('https://api.example.com/data');
Managing Form State
Example:
useFormInput
import { useState } from 'react'; function useFormInput(initialValue) { const [value, setValue] = useState(initialValue); const handleChange = (e) => { setValue(e.target.value); }; return { value, onChange: handleChange, }; } export default useFormInput;
Usage:
function MyForm() { const name = useFormInput(''); const email = useFormInput(''); return ( <form> <input type="text" placeholder="Name" {...name} /> <input type="email" placeholder="Email" {...email} /> </form> ); }
Debouncing Values (e.g., for search inputs)
Example:
useDebounce
import { useState, useEffect } from 'react'; function useDebounce(value, delay) { 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 = useDebounce(inputValue, 500);
Using Local Storage
Example:
useLocalStorage
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.log(error); return initialValue; } }); const setValue = (value) => { try { setStoredValue(value); window.localStorage.setItem(key, JSON.stringify(value)); } catch (error) { console.log(error); } }; return [storedValue, setValue]; } export default useLocalStorage;
Usage:
const [name, setName] = useLocalStorage('name', 'John');
6. Rules of Hooks
When creating custom hooks, the same rules of hooks that apply to built-in React hooks also apply:
Only call hooks at the top level: Don't call hooks inside loops, conditions, or nested functions.
Only call hooks from React functions: Hooks should only be called in functional components or within other custom hooks.
7. Custom Hooks with Parameters
Custom hooks can accept arguments to customize their behavior.
For example, modifying the useFetch
hook to take a dependency array:
function useFetch(url, deps = []) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
async function fetchData() {
try {
const response = await fetch(url);
const result = await response.json();
setData(result);
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
}
fetchData();
}, deps); // Notice the deps array
return { data, loading, error };
}
8. Advanced Example: Combining Multiple Hooks
Sometimes you need more advanced logic that involves multiple hooks. Here’s an example of using both useState
and useEffect
within a custom hook:
Example: useDocumentTitle
Hook
import { useState, useEffect } from 'react';
function useDocumentTitle(title) {
useEffect(() => {
document.title = title;
}, [title]);
}
function useCounter(initialValue = 0) {
const [count, setCount] = useState(initialValue);
const increment = () => setCount((prev) => prev + 1);
const decrement = () => setCount((prev) => prev - 1);
return { count, increment, decrement };
}
export default function CounterComponent() {
const { count, increment, decrement } = useCounter(5);
useDocumentTitle(`Counter: ${count}`);
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
</div>
);
}