Examples
Learn React Dev Debugger through practical, real-world examples! Each example includes complete code and explains what you'll learn.
🎯 Basic Examples
Simple Counter
The classic counter example to understand basic state tracking.
What you'll learn:
- How to replace
useStatewithuseTrackedState - Viewing state updates in the timeline
- Understanding state snapshots
import { useTrackedState } from 'react-dev-debugger';
export function Counter() {
const [count, setCount] = useTrackedState(0, 'counter');
return (
<div>
<h2>Count: {count}</h2>
<button onClick={() => setCount(count + 1)}>
+1
</button>
<button onClick={() => setCount(count - 1)}>
-1
</button>
<button onClick={() => setCount(0)}>
Reset
</button>
</div>
);
}
Try it out:
- Click the buttons
- Open the debugger panel (Ctrl/Cmd + Shift + D)
- See each increment/decrement in the timeline
- Click on any update to see before/after values
Form with Multiple States
Track multiple related states in a single component.
What you'll learn:
- Managing multiple tracked states
- Identifying which field caused a re-render
- Debugging form validation
import { useTrackedState } from 'react-dev-debugger';
interface FormData {
name: string;
email: string;
age: number;
}
export function UserForm() {
const [name, setName] = useTrackedState('', 'formName');
const [email, setEmail] = useTrackedState('', 'formEmail');
const [age, setAge] = useTrackedState(0, 'formAge');
const [errors, setErrors] = useTrackedState<string[]>([], 'formErrors');
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
const newErrors: string[] = [];
if (!name) newErrors.push('Name is required');
if (!email.includes('@')) newErrors.push('Invalid email');
if (age < 18) newErrors.push('Must be 18 or older');
setErrors(newErrors);
if (newErrors.length === 0) {
console.log('Form submitted:', { name, email, age });
}
};
return (
<form onSubmit={handleSubmit}>
<div>
<label>Name:</label>
<input
value={name}
onChange={(e) => setName(e.target.value)}
/>
</div>
<div>
<label>Email:</label>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
</div>
<div>
<label>Age:</label>
<input
type="number"
value={age}
onChange={(e) => setAge(Number(e.target.value))}
/>
</div>
{errors.length > 0 && (
<div style={{ color: 'red' }}>
{errors.map((error, i) => (
<div key={i}>{error}</div>
))}
</div>
)}
<button type="submit">Submit</button>
</form>
);
}
Debug insights:
- Watch how each field update appears separately
- See the validation errors state update on submit
- Use time-travel to go back to any form state
🔥 Advanced Examples
Todo List with useReducer
A classic todo list using useTrackedReducer to see all actions.
What you'll learn:
- Tracking reducer actions
- Viewing action types in the debugger
- Understanding complex state updates
import { useTrackedReducer } from 'react-dev-debugger';
interface Todo {
id: number;
text: string;
completed: boolean;
}
type TodoAction =
| { type: 'ADD_TODO'; text: string }
| { type: 'TOGGLE_TODO'; id: number }
| { type: 'DELETE_TODO'; id: number }
| { type: 'EDIT_TODO'; id: number; text: string }
| { type: 'CLEAR_COMPLETED' };
function todoReducer(state: Todo[], action: TodoAction): Todo[] {
switch (action.type) {
case 'ADD_TODO':
return [
...state,
{
id: Date.now(),
text: action.text,
completed: false,
},
];
case 'TOGGLE_TODO':
return state.map(todo =>
todo.id === action.id
? { ...todo, completed: !todo.completed }
: todo
);
case 'DELETE_TODO':
return state.filter(todo => todo.id !== action.id);
case 'EDIT_TODO':
return state.map(todo =>
todo.id === action.id
? { ...todo, text: action.text }
: todo
);
case 'CLEAR_COMPLETED':
return state.filter(todo => !todo.completed);
default:
return state;
}
}
export function TodoList() {
const [todos, dispatch] = useTrackedReducer(
todoReducer,
[],
'todos'
);
const [input, setInput] = useTrackedState('', 'todoInput');
const handleAdd = () => {
if (input.trim()) {
dispatch({ type: 'ADD_TODO', text: input });
setInput('');
}
};
return (
<div>
<h2>Todo List</h2>
<div>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && handleAdd()}
placeholder="What needs to be done?"
/>
<button onClick={handleAdd}>Add</button>
</div>
<ul>
{todos.map(todo => (
<li key={todo.id}>
<input
type="checkbox"
checked={todo.completed}
onChange={() => dispatch({ type: 'TOGGLE_TODO', id: todo.id })}
/>
<span
style={{
textDecoration: todo.completed ? 'line-through' : 'none'
}}
>
{todo.text}
</span>
<button
onClick={() => dispatch({ type: 'DELETE_TODO', id: todo.id })}
>
Delete
</button>
</li>
))}
</ul>
{todos.some(t => t.completed) && (
<button onClick={() => dispatch({ type: 'CLEAR_COMPLETED' })}>
Clear Completed
</button>
)}
<div>
<strong>Stats:</strong> {todos.length} total,{' '}
{todos.filter(t => t.completed).length} completed
</div>
</div>
);
}
Debug insights:
- Each action type appears in the timeline
- See the full action object with its payload
- Step back to undo any action
- Watch the dependency graph update
Shopping Cart
A real-world shopping cart with multiple components and complex state.
What you'll learn:
- Multiple components sharing state
- Complex state updates
- Parent-child component relationships in the graph
import { useTrackedState, useTrackedReducer } from 'react-dev-debugger';
interface Product {
id: number;
name: string;
price: number;
image: string;
}
interface CartItem extends Product {
quantity: number;
}
type CartAction =
| { type: 'ADD_TO_CART'; product: Product }
| { type: 'REMOVE_FROM_CART'; productId: number }
| { type: 'UPDATE_QUANTITY'; productId: number; quantity: number }
| { type: 'CLEAR_CART' };
function cartReducer(state: CartItem[], action: CartAction): CartItem[] {
switch (action.type) {
case 'ADD_TO_CART': {
const existing = state.find(item => item.id === action.product.id);
if (existing) {
return state.map(item =>
item.id === action.product.id
? { ...item, quantity: item.quantity + 1 }
: item
);
}
return [...state, { ...action.product, quantity: 1 }];
}
case 'REMOVE_FROM_CART':
return state.filter(item => item.id !== action.productId);
case 'UPDATE_QUANTITY':
return state.map(item =>
item.id === action.productId
? { ...item, quantity: action.quantity }
: item
);
case 'CLEAR_CART':
return [];
default:
return state;
}
}
const PRODUCTS: Product[] = [
{ id: 1, name: 'React Hoodie', price: 49.99, image: '👕' },
{ id: 2, name: 'TypeScript Mug', price: 19.99, image: '☕' },
{ id: 3, name: 'Debug Stickers', price: 9.99, image: '🔖' },
];
export function ShoppingCart() {
const [cart, dispatch] = useTrackedReducer(cartReducer, [], 'cart');
const [showCart, setShowCart] = useTrackedState(false, 'showCart');
const total = cart.reduce((sum, item) => sum + item.price * item.quantity, 0);
return (
<div>
<h1>Online Store</h1>
<button onClick={() => setShowCart(!showCart)}>
🛒 Cart ({cart.reduce((sum, item) => sum + item.quantity, 0)})
</button>
{showCart && (
<div style={{ border: '1px solid #ccc', padding: '1rem' }}>
<h2>Shopping Cart</h2>
{cart.length === 0 ? (
<p>Cart is empty</p>
) : (
<>
{cart.map(item => (
<div key={item.id} style={{ marginBottom: '1rem' }}>
<span>{item.image} {item.name}</span>
<span> - ${item.price}</span>
<input
type="number"
value={item.quantity}
min="1"
onChange={(e) =>
dispatch({
type: 'UPDATE_QUANTITY',
productId: item.id,
quantity: Number(e.target.value),
})
}
/>
<button
onClick={() =>
dispatch({
type: 'REMOVE_FROM_CART',
productId: item.id,
})
}
>
Remove
</button>
</div>
))}
<div>
<strong>Total: ${total.toFixed(2)}</strong>
</div>
<button onClick={() => dispatch({ type: 'CLEAR_CART' })}>
Clear Cart
</button>
</>
)}
</div>
)}
<h2>Products</h2>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '1rem' }}>
{PRODUCTS.map(product => (
<div key={product.id} style={{ border: '1px solid #ddd', padding: '1rem' }}>
<div style={{ fontSize: '3rem' }}>{product.image}</div>
<h3>{product.name}</h3>
<p>${product.price}</p>
<button
onClick={() =>
dispatch({ type: 'ADD_TO_CART', product })
}
>
Add to Cart
</button>
</div>
))}
</div>
</div>
);
}
Debug insights:
- Watch cart state evolve with each action
- See how quantity updates differ from additions
- Track the showCart toggle separately
- Use time-travel to test edge cases
🎨 Component Patterns
Context API Integration
Track context values across multiple consumers.
What you'll learn:
- Tracking context updates
- Seeing which components consume context
- Understanding re-render propagation
import { createContext, useContext } from 'react';
import { useTrackedState } from 'react-dev-debugger';
interface Theme {
primary: string;
background: string;
text: string;
}
const themes: Record<string, Theme> = {
light: {
primary: '#007bff',
background: '#ffffff',
text: '#000000',
},
dark: {
primary: '#0dcaf0',
background: '#1a1a1a',
text: '#ffffff',
},
};
const ThemeContext = createContext<{
theme: Theme;
themeName: string;
setTheme: (name: string) => void;
} | null>(null);
export function ThemeProvider({ children }: { children: React.ReactNode }) {
const [themeName, setThemeName] = useTrackedState('light', 'themeName');
const value = {
theme: themes[themeName],
themeName,
setTheme: setThemeName,
};
return (
<ThemeContext.Provider value={value}>
{children}
</ThemeContext.Provider>
);
}
export function useTheme() {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTheme must be used within ThemeProvider');
}
return context;
}
// Usage example
export function ThemedButton() {
const { theme, themeName, setTheme } = useTheme();
return (
<button
style={{
background: theme.primary,
color: theme.background,
padding: '0.5rem 1rem',
border: 'none',
borderRadius: '4px',
}}
onClick={() => setTheme(themeName === 'light' ? 'dark' : 'light')}
>
Toggle Theme
</button>
);
}
export function ThemedApp() {
const { theme } = useTheme();
return (
<div
style={{
background: theme.background,
color: theme.text,
minHeight: '100vh',
padding: '2rem',
}}
>
<h1>Themed App</h1>
<ThemedButton />
</div>
);
}
Custom Hook Pattern
Create reusable custom hooks with tracking.
import { useTrackedState } from 'react-dev-debugger';
import { useEffect } from 'react';
export function useLocalStorage<T>(
key: string,
initialValue: T
): [T, (value: T) => void] {
const [value, setValue] = useTrackedState<T>(initialValue, `localStorage:${key}`);
// Load from localStorage on mount
useEffect(() => {
const stored = localStorage.getItem(key);
if (stored) {
try {
setValue(JSON.parse(stored));
} catch (e) {
console.error('Failed to parse localStorage value:', e);
}
}
}, [key]);
// Save to localStorage on change
useEffect(() => {
localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
return [value, setValue];
}
// Usage
export function UserPreferences() {
const [fontSize, setFontSize] = useLocalStorage('fontSize', 16);
const [darkMode, setDarkMode] = useLocalStorage('darkMode', false);
return (
<div>
<label>
Font Size: {fontSize}px
<input
type="range"
min="12"
max="24"
value={fontSize}
onChange={(e) => setFontSize(Number(e.target.value))}
/>
</label>
<label>
<input
type="checkbox"
checked={darkMode}
onChange={(e) => setDarkMode(e.target.checked)}
/>
Dark Mode
</label>
</div>
);
}
📦 Store Integration Examples
Redux Adapter
See the Redux Integration Guide
Zustand Adapter
See the Zustand Integration Guide
Jotai Adapter
See the Jotai Integration Guide
💡 Pro Tips
Meaningful Debug Labels
Always use descriptive labels for your tracked state:
// ❌ Bad - unclear
const [data, setData] = useTrackedState({}, 'data');
// ✅ Good - descriptive
const [userData, setUserData] = useTrackedState({}, 'userData');
const [cartData, setCartData] = useTrackedState({}, 'cartData');
Organize by Feature
Group related states with consistent naming:
const [userName, setUserName] = useTrackedState('', 'user:name');
const [userEmail, setUserEmail] = useTrackedState('', 'user:email');
const [userAge, setUserAge] = useTrackedState(0, 'user:age');
Use Time-Travel for Testing
Instead of manually testing edge cases:
- Perform normal user actions
- Use time-travel to jump to any state
- Test different scenarios from that point
- Export snapshots for regression testing
🚀 Next Steps
- Learn about Store Adapters
- Read Performance Optimization
- Explore Advanced Features
- Check the API Reference