React Custom Hooks
Creating reusable logic with custom hooks
🎣 What are Custom Hooks?
Custom Hooks are JavaScript functions that let you extract and reuse stateful logic between components. They start with "use" and can call other hooks inside them.
import { useState, useEffect } from 'react';
function useWindowWidth() {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
const handleResize = () => setWidth(window.innerWidth);
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return width;
}
Custom Hook Concepts
Reusability
Share logic across multiple components
const data = useCustomHook();
Encapsulation
Hide complex logic in a single function
function useAuth() { /* logic */ }
Composition
Combine multiple hooks together
function useData() {
const [state] = useState();
useEffect(() => {});
}
Naming Convention
Must start with "use"
function useFetch() { }
🔹 Simple Custom Hook Example
Create a custom hook to manage form input state. This hook encapsulates the common pattern of handling input changes and can be reused across different forms.
import { useState } from 'react';
// Custom Hook
function useInput(initialValue) {
const [value, setValue] = useState(initialValue);
const handleChange = (e) => {
setValue(e.target.value);
};
const reset = () => {
setValue(initialValue);
};
return { value, onChange: handleChange, reset };
}
// Using the Custom Hook
function LoginForm() {
const email = useInput('');
const password = useInput('');
const handleSubmit = (e) => {
e.preventDefault();
console.log('Email:', email.value);
console.log('Password:', password.value);
email.reset();
password.reset();
};
return (
<form onSubmit={handleSubmit}>
<input {...email} type="email" placeholder="Email" />
<input {...password} type="password" placeholder="Password" />
<button type="submit">Login</button>
</form>
);
}
Output:
🔹 useFetch Custom Hook
A practical custom hook for data fetching. This hook handles loading states, errors, and data management, making API calls simpler and more consistent across your app.
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 {
setLoading(true);
const response = await fetch(url);
if (!response.ok) throw new Error('Failed to fetch');
const result = await response.json();
setData(result);
setError(null);
} catch (err) {
setError(err.message);
setData(null);
} finally {
setLoading(false);
}
};
fetchData();
}, [url]);
return { data, loading, error };
}
// Using the Custom Hook
function UserProfile({ userId }) {
const { data, loading, error } = useFetch(`/api/users/${userId}`);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error}</p>;
return (
<div>
<h2>{data.name}</h2>
<p>{data.email}</p>
</div>
);
}
Output:
John Doe
🔹 useLocalStorage Custom Hook
Sync state with localStorage automatically. This hook persists data across page refreshes and provides a simple API similar to useState.
import { useState, useEffect } from 'react';
function useLocalStorage(key, initialValue) {
// Get initial value from localStorage or use provided initial value
const [value, setValue] = useState(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
console.error(error);
return initialValue;
}
});
// Update localStorage when value changes
useEffect(() => {
try {
window.localStorage.setItem(key, JSON.stringify(value));
} catch (error) {
console.error(error);
}
}, [key, value]);
return [value, setValue];
}
// Using the Custom Hook
function ThemeToggle() {
const [theme, setTheme] = useLocalStorage('theme', 'light');
return (
<div>
<p>Current theme: {theme}</p>
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
Toggle Theme
</button>
</div>
);
}
🔹 useToggle Custom Hook
Simplify boolean state management with a toggle hook. Perfect for modals, dropdowns, and any on/off state.
import { useState } from 'react';
function useToggle(initialValue = false) {
const [value, setValue] = useState(initialValue);
const toggle = () => setValue(prev => !prev);
const setTrue = () => setValue(true);
const setFalse = () => setValue(false);
return { value, toggle, setTrue, setFalse };
}
// Using the Custom Hook
function Modal() {
const modal = useToggle(false);
return (
<div>
<button onClick={modal.setTrue}>Open Modal</button>
{modal.value && (
<div className="modal">
<h2>Modal Content</h2>
<button onClick={modal.setFalse}>Close</button>
</div>
)}
</div>
);
}
Output:
🔹 Custom Hook Best Practices
Follow these guidelines when creating custom hooks:
✅ Do:
- Start with "use": Required naming convention (useMyHook)
- Extract reusable logic: Logic used in multiple components
- Return useful values: State, functions, or objects
- Keep them focused: One responsibility per hook
- Document parameters: Clear what inputs are needed
❌ Don't:
- Call conditionally: Hooks must be called in the same order
- Call in loops: Violates Rules of Hooks
- Call in regular functions: Only in components or other hooks
- Make them too complex: Break into smaller hooks if needed
🔹 useDebounce Custom Hook
Delay updating a value until after a pause in changes. Useful for search inputs and API calls.
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;
}
// Using the Custom Hook
function SearchBar() {
const [searchTerm, setSearchTerm] = useState('');
const debouncedSearch = useDebounce(searchTerm, 500);
useEffect(() => {
if (debouncedSearch) {
// API call happens only after user stops typing for 500ms
console.log('Searching for:', debouncedSearch);
}
}, [debouncedSearch]);
return (
<input
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
placeholder="Search..."
/>
);
}