useState Hook
function Counter() {
const [count, setCount] = useState(0);
const [user, setUser] = useState({ name: '', email: '' });
const [items, setItems] = useState([]);
const increment = () => setCount(prev => prev + 1);
const decrement = () => setCount(prev => prev - 1);
const updateUser = (field, value) => {
setUser(prev => ({ ...prev, [field]: value }));
};
const addItem = (item) => {
setItems(prev => [...prev, item]);
};
const removeItem = (id) => {
setItems(prev => prev.filter(item => item.id !== id));
};
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
</div>
);
}
useReducer Hook
function todoReducer(state, action) {
switch (action.type) {
case 'ADD_TODO':
return {
...state,
todos: [...state.todos, {
id: Date.now(),
text: action.payload,
completed: false
}]
};
case 'TOGGLE_TODO':
return {
...state,
todos: state.todos.map(todo =>
todo.id === action.payload
? { ...todo, completed: !todo.completed }
: todo
)
};
case 'DELETE_TODO':
return {
...state,
todos: state.todos.filter(todo => todo.id !== action.payload)
};
case 'SET_FILTER':
return {
...state,
filter: action.payload
};
default:
return state;
}
}
function TodoApp() {
const [state, dispatch] = useReducer(todoReducer, {
todos: [],
filter: 'all'
});
const addTodo = (text) => {
dispatch({ type: 'ADD_TODO', payload: text });
};
const toggleTodo = (id) => {
dispatch({ type: 'TOGGLE_TODO', payload: id });
};
const deleteTodo = (id) => {
dispatch({ type: 'DELETE_TODO', payload: id });
};
return (
<div>
<TodoForm onAdd={addTodo} />
<TodoList
todos={state.todos}
onToggle={toggleTodo}
onDelete={deleteTodo}
/>
</div>
);
}
Context API
Basic Context Setup
const UserContext = createContext();
const ThemeContext = createContext();
function AppProvider({ children }) {
const [user, setUser] = useState(null);
const [theme, setTheme] = useState('light');
const login = (userData) => {
setUser(userData);
};
const logout = () => {
setUser(null);
};
const toggleTheme = () => {
setTheme(prev => prev === 'light' ? 'dark' : 'light');
};
return (
<UserContext.Provider value={{ user, login, logout }}>
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
</UserContext.Provider>
);
}
function useUser() {
const context = useContext(UserContext);
if (!context) {
throw new Error('useUser must be used within UserProvider');
}
return context;
}
function useTheme() {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTheme must be used within ThemeProvider');
}
return context;
}
function Header() {
const { user, logout } = useUser();
const { theme, toggleTheme } = useTheme();
return (
<header className={theme}>
{user ? (
<div>
<span>Welcome, {user.name}</span>
<button onClick={logout}>Logout</button>
</div>
) : (
<span>Please login</span>
)}
<button onClick={toggleTheme}>Toggle Theme</button>
</header>
);
}
Context with useReducer
function cartReducer(state, action) {
switch (action.type) {
case 'ADD_ITEM':
const existingItem = state.items.find(item => item.id === action.payload.id);
if (existingItem) {
return {
...state,
items: state.items.map(item =>
item.id === action.payload.id
? { ...item, quantity: item.quantity + 1 }
: item
)
};
}
return {
...state,
items: [...state.items, { ...action.payload, quantity: 1 }]
};
case 'REMOVE_ITEM':
return {
...state,
items: state.items.filter(item => item.id !== action.payload)
};
case 'UPDATE_QUANTITY':
return {
...state,
items: state.items.map(item =>
item.id === action.payload.id
? { ...item, quantity: action.payload.quantity }
: item
)
};
case 'CLEAR_CART':
return {
...state,
items: []
};
default:
return state;
}
}
const CartContext = createContext();
function CartProvider({ children }) {
const [state, dispatch] = useReducer(cartReducer, {
items: []
});
const addItem = (item) => {
dispatch({ type: 'ADD_ITEM', payload: item });
};
const removeItem = (itemId) => {
dispatch({ type: 'REMOVE_ITEM', payload: itemId });
};
const updateQuantity = (itemId, quantity) => {
dispatch({ type: 'UPDATE_QUANTITY', payload: { id: itemId, quantity } });
};
const clearCart = () => {
dispatch({ type: 'CLEAR_CART' });
};
const total = state.items.reduce((sum, item) => sum + (item.price * item.quantity), 0);
return (
<CartContext.Provider value={{
items: state.items,
total,
addItem,
removeItem,
updateQuantity,
clearCart
}}>
{children}
</CartContext.Provider>
);
}
Store Setup
import { configureStore, createSlice } from '@reduxjs/toolkit';
const userSlice = createSlice({
name: 'user',
initialState: {
currentUser: null,
loading: false,
error: null
},
reducers: {
setUser: (state, action) => {
state.currentUser = action.payload;
},
setLoading: (state, action) => {
state.loading = action.payload;
},
setError: (state, action) => {
state.error = action.payload;
},
logout: (state) => {
state.currentUser = null;
state.error = null;
}
}
});
const cartSlice = createSlice({
name: 'cart',
initialState: {
items: [],
total: 0
},
reducers: {
addItem: (state, action) => {
const existingItem = state.items.find(item => item.id === action.payload.id);
if (existingItem) {
existingItem.quantity += 1;
} else {
state.items.push({ ...action.payload, quantity: 1 });
}
state.total = state.items.reduce((sum, item) => sum + (item.price * item.quantity), 0);
},
removeItem: (state, action) => {
state.items = state.items.filter(item => item.id !== action.payload);
state.total = state.items.reduce((sum, item) => sum + (item.price * item.quantity), 0);
},
updateQuantity: (state, action) => {
const { id, quantity } = action.payload;
const item = state.items.find(item => item.id === id);
if (item) {
item.quantity = quantity;
state.total = state.items.reduce((sum, item) => sum + (item.price * item.quantity), 0);
}
}
}
});
const store = configureStore({
reducer: {
user: userSlice.reducer,
cart: cartSlice.reducer
}
});
export const { setUser, setLoading, setError, logout } = userSlice.actions;
export const { addItem, removeItem, updateQuantity } = cartSlice.actions;
export default store;
Redux Hooks Usage
import { useSelector, useDispatch } from 'react-redux';
import { setUser, addItem, removeItem } from './store';
function UserProfile() {
const dispatch = useDispatch();
const user = useSelector(state => state.user.currentUser);
const loading = useSelector(state => state.user.loading);
const handleLogin = (userData) => {
dispatch(setUser(userData));
};
const handleLogout = () => {
dispatch(logout());
};
if (loading) return <div>Loading...</div>;
return (
<div>
{user ? (
<div>
<h1>Welcome, {user.name}</h1>
<button onClick={handleLogout}>Logout</button>
</div>
) : (
<LoginForm onLogin={handleLogin} />
)}
</div>
);
}
function Cart() {
const dispatch = useDispatch();
const items = useSelector(state => state.cart.items);
const total = useSelector(state => state.cart.total);
const handleAddItem = (item) => {
dispatch(addItem(item));
};
const handleRemoveItem = (itemId) => {
dispatch(removeItem(itemId));
};
return (
<div>
<h2>Cart ({items.length} items)</h2>
{items.map(item => (
<div key={item.id}>
<span>{item.name} x {item.quantity}</span>
<button onClick={() => handleRemoveItem(item.id)}>Remove</button>
</div>
))}
<p>Total: ${total}</p>
</div>
);
}
Store Setup
import { create } from 'zustand';
const useCounterStore = create((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
reset: () => set({ count: 0 })
}));
const useUserStore = create((set, get) => ({
user: null,
loading: false,
error: null,
setUser: (user) => set({ user }),
setLoading: (loading) => set({ loading }),
setError: (error) => set({ error }),
login: async (credentials) => {
set({ loading: true, error: null });
try {
const user = await loginAPI(credentials);
set({ user, loading: false });
} catch (error) {
set({ error: error.message, loading: false });
}
},
logout: () => set({ user: null, error: null }),
updateProfile: async (updates) => {
const { user } = get();
if (!user) return;
set({ loading: true });
try {
const updatedUser = await updateProfileAPI(user.id, updates);
set({ user: updatedUser, loading: false });
} catch (error) {
set({ error: error.message, loading: false });
}
}
}));
const useCartStore = create(
persist(
(set, get) => ({
items: [],
addItem: (item) => {
const { items } = get();
const existingItem = items.find(i => i.id === item.id);
if (existingItem) {
set({
items: items.map(i =>
i.id === item.id ? { ...i, quantity: i.quantity + 1 } : i
)
});
} else {
set({ items: [...items, { ...item, quantity: 1 }] });
}
},
removeItem: (itemId) => {
const { items } = get();
set({ items: items.filter(item => item.id !== itemId) });
},
updateQuantity: (itemId, quantity) => {
const { items } = get();
set({
items: items.map(item =>
item.id === itemId ? { ...item, quantity } : item
)
});
},
clearCart: () => set({ items: [] }),
getTotal: () => {
const { items } = get();
return items.reduce((sum, item) => sum + (item.price * item.quantity), 0);
}
}),
{
name: 'cart-storage',
partialize: (state) => ({ items: state.items })
}
)
);
Zustand Usage
function Counter() {
const { count, increment, decrement, reset } = useCounterStore();
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
<button onClick={reset}>Reset</button>
</div>
);
}
function UserProfile() {
const { user, loading, error, login, logout, updateProfile } = useUserStore();
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
return (
<div>
{user ? (
<div>
<h1>Welcome, {user.name}</h1>
<button onClick={logout}>Logout</button>
<button onClick={() => updateProfile({ name: 'New Name' })}>
Update Profile
</button>
</div>
) : (
<LoginForm onLogin={login} />
)}
</div>
);
}
function Cart() {
const { items, addItem, removeItem, updateQuantity, getTotal } = useCartStore();
const total = getTotal();
return (
<div>
<h2>Cart ({items.length} items)</h2>
{items.map(item => (
<div key={item.id}>
<span>{item.name} x {item.quantity}</span>
<input
type="number"
value={item.quantity}
onChange={(e) => updateQuantity(item.id, parseInt(e.target.value))}
min="1"
/>
<button onClick={() => removeItem(item.id)}>Remove</button>
</div>
))}
<p>Total: ${total}</p>
</div>
);
}
Compound Components
const ToggleContext = createContext();
function Toggle({ children, onToggle }) {
const [on, setOn] = useState(false);
const toggle = useCallback(() => {
setOn(prev => !prev);
onToggle?.(!on);
}, [on, onToggle]);
return (
<ToggleContext.Provider value={{ on, toggle }}>
{children}
</ToggleContext.Provider>
);
}
function ToggleOn({ children }) {
const { on } = useContext(ToggleContext);
return on ? children : null;
}
function ToggleOff({ children }) {
const { on } = useContext(ToggleContext);
return on ? null : children;
}
function ToggleButton() {
const { on, toggle } = useContext(ToggleContext);
return <button onClick={toggle}>{on ? 'ON' : 'OFF'}</button>;
}
function App() {
return (
<Toggle onToggle={console.log}>
<ToggleOn>The button is on</ToggleOn>
<ToggleOff>The button is off</ToggleOff>
<ToggleButton />
</Toggle>
);
}
Render Props Pattern
function MouseTracker({ render }) {
const [position, setPosition] = useState({ x: 0, y: 0 });
useEffect(() => {
const handleMouseMove = (event) => {
setPosition({ x: event.clientX, y: event.clientY });
};
window.addEventListener('mousemove', handleMouseMove);
return () => window.removeEventListener('mousemove', handleMouseMove);
}, []);
return render(position);
}
function App() {
return (
<MouseTracker
render={({ x, y }) => (
<div>
Mouse position: {x}, {y}
</div>
)}
/>
);
}
State Structure
const state = {
users: {
byId: {
1: { id: 1, name: 'John', email: 'john@example.com' },
2: { id: 2, name: 'Jane', email: 'jane@example.com' }
},
allIds: [1, 2]
},
posts: {
byId: {
1: { id: 1, title: 'Post 1', authorId: 1 },
2: { id: 2, title: 'Post 2', authorId: 2 }
},
allIds: [1, 2]
}
};
const state = {
users: [
{
id: 1,
name: 'John',
posts: [
{ id: 1, title: 'Post 1' },
{ id: 2, title: 'Post 2' }
]
}
]
};
Performance Optimization
const selectUserById = (state, userId) => state.users.byId[userId];
const selectUserPosts = (state, userId) =>
state.posts.allIds
.map(id => state.posts.byId[id])
.filter(post => post.authorId === userId);
const selectUser = useSelector(
state => state.users.byId[userId],
shallowEqual
);
const UserProfile = memo(({ userId }) => {
const user = useSelector(state => selectUserById(state, userId));
return <div>{user.name}</div>;
});
Error Boundaries
class StateErrorBoundary extends Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
console.error('State error:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return (
<div>
<h2>Something went wrong with state management.</h2>
<button onClick={() => this.setState({ hasError: false })}>
Try again
</button>
</div>
);
}
return this.props.children;
}
}
Testing State
import { render, screen } from '@testing-library/react';
import { Provider } from 'react-redux';
import configureStore from 'redux-mock-store';
const mockStore = configureStore([]);
test('renders user profile', () => {
const store = mockStore({
user: { currentUser: { name: 'John', email: 'john@example.com' } }
});
render(
<Provider store={store}>
<UserProfile />
</Provider>
);
expect(screen.getByText('Welcome, John')).toBeInTheDocument();
});
import { renderHook, act } from '@testing-library/react';
import { useCounterStore } from './store';
test('increments counter', () => {
const { result } = renderHook(() => useCounterStore());
act(() => {
result.current.increment();
});
expect(result.current.count).toBe(1);
});