React Hooks
Master React Hooks for state management and side effects
React Hooks
Hooks let you use state and other React features in function components.
useState Hook
import { useState } from 'react';
function Counter() {
// Declare state variable with initial value
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
<button onClick={() => setCount(count - 1)}>
Decrement
</button>
<button onClick={() => setCount(0)}>
Reset
</button>
</div>
);
}
// State with objects
function UserForm() {
const [user, setUser] = useState({
name: '',
email: '',
age: 0
});
const updateUser = (field, value) => {
setUser(prevUser => ({
...prevUser,
[field]: value
}));
};
return (
<form>
<input
value={user.name}
onChange={(e) => updateUser('name', e.target.value)}
placeholder="Name"
/>
<input
value={user.email}
onChange={(e) => updateUser('email', e.target.value)}
placeholder="Email"
/>
<input
type="number"
value={user.age}
onChange={(e) => updateUser('age', parseInt(e.target.value))}
placeholder="Age"
/>
</form>
);
}
useEffect Hook
import { useState, useEffect } from 'react';
function DataFetcher() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
// Effect runs after every render
useEffect(() => {
console.log('Component rendered');
});
// Effect runs only once (on mount)
useEffect(() => {
fetchData();
}, []); // Empty dependency array
// Effect runs when specific values change
useEffect(() => {
document.title = `Data: ${data?.length || 0} items`;
}, [data]); // Runs when 'data' changes
// Effect with cleanup
useEffect(() => {
const timer = setInterval(() => {
console.log('Timer tick');
}, 1000);
// Cleanup function
return () => {
clearInterval(timer);
};
}, []);
const fetchData = async () => {
try {
setLoading(true);
const response = await fetch('/api/data');
const result = await response.json();
setData(result);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
return (
<div>
<h2>Data ({data?.length} items)</h2>
<ul>
{data?.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</div>
);
}
useContext Hook
import { createContext, useContext, useState } from 'react';
// Create context
const ThemeContext = createContext();
// Provider component
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
const toggleTheme = () => {
setTheme(prevTheme => prevTheme === 'light' ? 'dark' : 'light');
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}
// Consumer component
function ThemedButton() {
const { theme, toggleTheme } = useContext(ThemeContext);
return (
<button
onClick={toggleTheme}
className={`btn btn-${theme}`}
>
Current theme: {theme}
</button>
);
}
// App component
function App() {
return (
<ThemeProvider>
<div>
<h1>My App</h1>
<ThemedButton />
</div>
</ThemeProvider>
);
}
useReducer Hook
import { useReducer } from 'react';
// Reducer function
function counterReducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
case 'reset':
return { count: 0 };
case 'set':
return { count: action.payload };
default:
throw new Error(`Unknown action: ${action.type}`);
}
}
function Counter() {
const [state, dispatch] = useReducer(counterReducer, { count: 0 });
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>
+
</button>
<button onClick={() => dispatch({ type: 'decrement' })}>
-
</button>
<button onClick={() => dispatch({ type: 'reset' })}>
Reset
</button>
<button onClick={() => dispatch({ type: 'set', payload: 10 })}>
Set to 10
</button>
</div>
);
}
Custom Hooks
// Custom hook for local storage
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 saving to localStorage:', error);
}
};
return [storedValue, setValue];
}
// Custom hook for API calls
function useApi(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);
const result = await response.json();
setData(result);
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
};
fetchData();
}, [url]);
return { data, loading, error };
}
// Using custom hooks
function UserProfile({ userId }) {
const [preferences, setPreferences] = useLocalStorage('userPrefs', {});
const { data: user, loading, error } = useApi(`/api/users/${userId}`);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error loading user</div>;
return (
<div>
<h1>{user.name}</h1>
<p>Theme: {preferences.theme || 'default'}</p>
<button onClick={() => setPreferences({ ...preferences, theme: 'dark' })}>
Set Dark Theme
</button>
</div>
);
}
Other Useful Hooks
import {
useRef,
useMemo,
useCallback,
useLayoutEffect
} from 'react';
function OptimizedComponent({ items, onItemClick }) {
const inputRef = useRef(null);
// Memoize expensive calculations
const expensiveValue = useMemo(() => {
return items.reduce((sum, item) => sum + item.value, 0);
}, [items]);
// Memoize callback functions
const handleClick = useCallback((item) => {
onItemClick(item);
}, [onItemClick]);
// Focus input on mount
useLayoutEffect(() => {
inputRef.current?.focus();
}, []);
return (
<div>
<input ref={inputRef} placeholder="Search..." />
<p>Total value: {expensiveValue}</p>
{items.map(item => (
<button key={item.id} onClick={() => handleClick(item)}>
{item.name}
</button>
))}
</div>
);
}
Hooks make React components more powerful and reusable. Master them to write cleaner, more efficient code!