🎯 What are React Hooks?#
React Hooks are functions that let you use state and other React features in functional components. Introduced in React 16.8, they revolutionized how we write React applications by eliminating the need for class components.
✨ Why Hooks Matter#
- Simplified Code: Reduce boilerplate and improve readability
- Logic Reuse: Easier to share stateful logic between components
- Better Testing: Hooks are easier to test than class methods
- Performance: Better optimization opportunities
🪝 Basic Hooks#
1. useState - Managing State#
useState
is the most fundamental Hook for adding state to functional components.
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
2. useEffect - Side Effects#
useEffect
handles side effects like data fetching, subscriptions, or manually changing the DOM.
import React, { useState, useEffect } from 'react';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function fetchUser() {
const response = await fetch(`/api/users/${userId}`);
const userData = await response.json();
setUser(userData);
setLoading(false);
}
fetchUser();
}, [userId]); // Dependency array
if (loading) return <div>Loading...</div>;
return <div>{user.name}</div>;
}
🔧 Advanced Hooks#
1. useContext - Context API#
useContext
provides a way to pass data through the component tree without prop drilling.
import React, { createContext, useContext } from 'react';
const ThemeContext = createContext();
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
);
}
function ThemedButton() {
const { theme, setTheme } = useContext(ThemeContext);
return (
<button
style={{ background: theme === 'light' ? '#fff' : '#333' }}
onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}
>
Toggle Theme
</button>
);
}
2. useReducer - Complex State Logic#
useReducer
is useful for managing complex state logic with multiple sub-values.
import React, { useReducer } from 'react';
const initialState = { count: 0 };
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
case 'reset':
return { count: 0 };
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
Count: {state.count}
<button onClick={() => dispatch({type: 'increment'})}>+</button>
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
<button onClick={() => dispatch({type: 'reset'})}>Reset</button>
</div>
);
}
🎨 Custom Hooks#
Custom Hooks let you extract component logic into reusable functions.
1. useLocalStorage Hook#
import { useState, useEffect } 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) {
return initialValue;
}
});
const setValue = (value) => {
try {
setStoredValue(value);
window.localStorage.setItem(key, JSON.stringify(value));
} catch (error) {
console.error(error);
}
};
return [storedValue, setValue];
}
// Usage
function MyComponent() {
const [name, setName] = useLocalStorage('name', '');
return (
<input
value={name}
onChange={(e) => setName(e.target.value)}
/>
);
}
2. 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);
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>
);
}
🚀 Performance Optimization#
1. useMemo - Memoized Values#
import React, { useMemo } from 'react';
function ExpensiveComponent({ items, filter }) {
const expensiveValue = useMemo(() => {
return items
.filter(item => item.category === filter)
.reduce((sum, item) => sum + item.value, 0);
}, [items, filter]); // Only recalculate when dependencies change
return <div>Total: {expensiveValue}</div>;
}
2. useCallback - Memoized Functions#
import React, { useCallback, useState } from 'react';
function ParentComponent() {
const [count, setCount] = useState(0);
const [name, setName] = useState('');
const handleClick = useCallback(() => {
console.log('Button clicked');
}, []); // Empty dependency array means this never changes
return (
<div>
<input value={name} onChange={(e) => setName(e.target.value)} />
<ChildComponent onClick={handleClick} />
</div>
);
}
const ChildComponent = React.memo(({ onClick }) => {
return <button onClick={onClick}>Click me</button>;
});
📝 Best Practices#
- Only call Hooks at the top level - Don’t call Hooks inside loops, conditions, or nested functions
- Use the dependency array correctly - Include all values from component scope that are used inside the effect
- Extract custom Hooks - When you find yourself duplicating logic, extract it into a custom Hook
- Use useCallback and useMemo wisely - Don’t overuse them; they have their own overhead
- Keep effects focused - Each effect should have a single responsibility
🎯 Common Patterns#
1. Data Fetching Pattern#
function useApiData(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let cancelled = false;
async function fetchData() {
try {
setLoading(true);
const response = await fetch(url);
const result = await response.json();
if (!cancelled) {
setData(result);
}
} catch (err) {
if (!cancelled) {
setError(err);
}
} finally {
if (!cancelled) {
setLoading(false);
}
}
}
fetchData();
return () => {
cancelled = true;
};
}, [url]);
return { data, loading, error };
}
2. Form Handling Pattern#
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
}));
// Clear error when user starts typing
if (errors[name]) {
setErrors(prev => ({
...prev,
[name]: ''
}));
}
};
const handleSubmit = (onSubmit) => (e) => {
e.preventDefault();
onSubmit(values);
};
return {
values,
errors,
handleChange,
handleSubmit,
setErrors
};
}
🎉 Conclusion#
React Hooks have transformed how we write React applications. They make functional components more powerful and provide better ways to share logic between components. With practice, you’ll find Hooks to be more intuitive and flexible than class components.
Start with the basic Hooks (useState
, useEffect
) and gradually work your way up to custom Hooks and advanced patterns. The key is to understand the rules and best practices, then experiment with different patterns to find what works best for your use case.
Happy coding with React Hooks! 🚀