Complete Guide to React Hooks - From useState to Custom Hooks
React Hooks revolutionized how we write React components by allowing us to use state and other React features in functional components. This comprehensive guide will take you from basic hooks to advanced patterns.
What Are React Hooks?
React Hooks are functions that let you "hook into" React state and lifecycle features from functional components. Introduced in React 16.8, hooks provide a more direct API to React concepts you already know.
Essential Built-in Hooks
1. useState - Managing Component State
The useState
hook is the most fundamental hook for adding state to functional components.
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
const [name, setName] = useState('');
return (
<div>
<h2>Count: {count}</h2>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
<input
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Enter your name"
/>
</div>
);
}
Key Points:
useState
returns an array with the current state and a setter function- State updates are asynchronous and may be batched
- Use functional updates when the new state depends on the previous state
2. useEffect - Side Effects and Lifecycle
useEffect
handles side effects like data fetching, subscriptions, and DOM manipulation.
import React, { useState, useEffect } from 'react';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
// Effect runs after every render
fetchUser(userId).then(userData => {
setUser(userData);
setLoading(false);
});
}, [userId]); // Dependency array
useEffect(() => {
// Cleanup function
const timer = setInterval(() => {
console.log('Timer tick');
}, 1000);
return () => clearInterval(timer);
}, []); // Empty dependency array = runs once
if (loading) return <div>Loading...</div>;
return <div>{user?.name}</div>;
}
Dependency Array Patterns:
[]
- Run once on mount[value]
- Run when value changes- No array - Run on every render
3. useContext - Sharing Data
useContext
provides a way to pass data through the component tree without prop drilling.
import React, { createContext, useContext, useState } from 'react';
// Create context
const ThemeContext = createContext();
// Provider component
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
);
}
// Consumer component
function ThemedButton() {
const { theme, setTheme } = useContext(ThemeContext);
return (
<button
style={{
background: theme === 'light' ? '#fff' : '#333',
color: theme === 'light' ? '#333' : '#fff'
}}
onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}
>
Toggle Theme
</button>
);
}
Advanced Hooks
4. useReducer - Complex State Management
useReducer
is perfect for managing complex state logic with multiple sub-values.
import React, { useReducer } from 'react';
const initialState = { count: 0, step: 1 };
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { ...state, count: state.count + state.step };
case 'decrement':
return { ...state, count: state.count - state.step };
case 'setStep':
return { ...state, step: action.step };
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<h2>Count: {state.count}</h2>
<input
type="number"
value={state.step}
onChange={(e) => dispatch({ type: 'setStep', step: Number(e.target.value) })}
/>
<button onClick={() => dispatch({ type: 'increment' })}>
+
</button>
<button onClick={() => dispatch({ type: 'decrement' })}>
-
</button>
</div>
);
}
5. useMemo and useCallback - Performance Optimization
These hooks help optimize performance by memoizing values and functions.
import React, { useState, useMemo, useCallback } from 'react';
function ExpensiveComponent({ items, filter }) {
// Memoize expensive calculation
const filteredItems = useMemo(() => {
return items.filter(item => item.category === filter);
}, [items, filter]);
// Memoize callback function
const handleClick = useCallback((id) => {
console.log('Item clicked:', id);
}, []);
return (
<div>
{filteredItems.map(item => (
<div key={item.id} onClick={() => handleClick(item.id)}>
{item.name}
</div>
))}
</div>
);
}
Custom Hooks
Custom hooks let you extract component logic into reusable functions.
useLocalStorage Hook
import { useState, useEffect } from 'react';
function useLocalStorage(key, initialValue) {
// Get from local storage then parse stored json or return 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;
}
});
// Return a wrapped version of useState's setter function that persists the new value to localStorage
const setValue = (value) => {
try {
// Allow value to be a function so we have the same API as useState
const valueToStore = value instanceof Function ? value(storedValue) : value;
setStoredValue(valueToStore);
window.localStorage.setItem(key, JSON.stringify(valueToStore));
} catch (error) {
console.log(error);
}
};
return [storedValue, setValue];
}
// Usage
function MyComponent() {
const [name, setName] = useLocalStorage('name', '');
return (
<input
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Enter your name"
/>
);
}
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 {
setLoading(true);
const response = await fetch(url);
if (!response.ok) {
throw new Error('Network response was not ok');
}
const result = await response.json();
setData(result);
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
}
fetchData();
}, [url]);
return { data, loading, error };
}
// Usage
function UserList() {
const { data: users, loading, error } = useFetch('/api/users');
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<ul>
{users?.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
Best Practices
1. Rules of Hooks
- Only call hooks at the top level
- Don't call hooks inside loops, conditions, or nested functions
- Only call hooks from React function components or custom hooks
2. Dependency Arrays
- Always include all dependencies in useEffect dependency arrays
- Use ESLint plugin to catch missing dependencies
- Be careful with object and function dependencies
3. Performance Considerations
- Use useMemo for expensive calculations
- Use useCallback for functions passed to child components
- Avoid creating objects and functions in render
4. Custom Hook Guidelines
- Start with "use" prefix
- Extract logic that's used in multiple components
- Keep hooks focused on a single responsibility
- Return objects for multiple values, arrays for two values
Common Patterns
Form Handling with Hooks
function useForm(initialValues) {
const [values, setValues] = useState(initialValues);
const [errors, setErrors] = useState({});
const handleChange = (e) => {
const { name, value } = e.target;
setValues(prev => ({ ...prev, [name]: value }));
};
const handleSubmit = (onSubmit) => (e) => {
e.preventDefault();
onSubmit(values);
};
return { values, errors, handleChange, handleSubmit };
}
Debounced Search
function useDebounce(value, delay) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => {
clearTimeout(handler);
};
}, [value, delay]);
return debouncedValue;
}
Conclusion
React Hooks provide a powerful and flexible way to manage state and side effects in functional components. By understanding the core hooks and learning to create custom hooks, you can write more maintainable and reusable React code.
Next Steps
Ready to dive deeper into React development? Check out our comprehensive tutorials:
Have questions about React Hooks? Share your thoughts and experiences in the comments below!