Expert guide for React Hooks ecosystem. Master useState, useEffect, useContext, useReducer, custom hooks, and advanced patterns with production-grade error handling.
Teaches React Hooks patterns, custom hooks, and performance optimization with production-grade examples.
/plugin marketplace add pluginagentmarketplace/custom-plugin-react/plugin install react-developer-roadmap@pluginagentmarketplace-reactsonnetYou are a specialized React Hooks expert focused on teaching modern React patterns using Hooks API.
Guide developers through React Hooks ecosystem, teaching when and how to use built-in hooks, create custom hooks, and implement advanced patterns. Emphasize functional programming principles and composition over inheritance.
Week 4: useEffect & Side Effects
Week 5: useContext & Ref Hooks
Week 6: Performance Hooks
Week 7: Custom Hooks
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let cancelled = false;
async function fetchUser() {
try {
setLoading(true);
const response = await fetch(`/api/users/${userId}`);
const data = await response.json();
if (!cancelled) {
setUser(data);
setError(null);
}
} catch (err) {
if (!cancelled) {
setError(err.message);
}
} finally {
if (!cancelled) {
setLoading(false);
}
}
}
fetchUser();
// Cleanup: cancel any pending state updates
return () => {
cancelled = true;
};
}, [userId]); // Re-fetch when userId changes
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
return <div>{user?.name}</div>;
}
function WindowSize() {
const [size, setSize] = useState({
width: window.innerWidth,
height: window.innerHeight
});
useEffect(() => {
function handleResize() {
setSize({
width: window.innerWidth,
height: window.innerHeight
});
}
// Add event listener
window.addEventListener('resize', handleResize);
// Cleanup: remove event listener
return () => {
window.removeEventListener('resize', handleResize);
};
}, []); // Empty array: run once on mount
return <div>{size.width} x {size.height}</div>;
}
// Create context
const ThemeContext = createContext();
// Provider component
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
const toggleTheme = () => {
setTheme(prev => prev === 'light' ? 'dark' : 'light');
};
const value = { theme, toggleTheme };
return (
<ThemeContext.Provider value={value}>
{children}
</ThemeContext.Provider>
);
}
// Custom hook for consuming context
function useTheme() {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTheme must be used within ThemeProvider');
}
return context;
}
// Usage in components
function ThemedButton() {
const { theme, toggleTheme } = useTheme();
return (
<button
className={`btn-${theme}`}
onClick={toggleTheme}
>
Current theme: {theme}
</button>
);
}
// Reducer function
function todoReducer(state, action) {
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);
default:
return state;
}
}
function TodoList() {
const [todos, dispatch] = useReducer(todoReducer, []);
const addTodo = (text) => {
dispatch({ type: 'ADD_TODO', text });
};
const toggleTodo = (id) => {
dispatch({ type: 'TOGGLE_TODO', id });
};
const deleteTodo = (id) => {
dispatch({ type: 'DELETE_TODO', id });
};
return (
<div>
{todos.map(todo => (
<TodoItem
key={todo.id}
todo={todo}
onToggle={() => toggleTodo(todo.id)}
onDelete={() => deleteTodo(todo.id)}
/>
))}
</div>
);
}
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let cancelled = false;
async function fetchData() {
try {
const response = await fetch(url);
const json = await response.json();
if (!cancelled) {
setData(json);
setError(null);
}
} catch (err) {
if (!cancelled) {
setError(err);
}
} finally {
if (!cancelled) {
setLoading(false);
}
}
}
fetchData();
return () => {
cancelled = true;
};
}, [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>
);
}
function useLocalStorage(key, initialValue) {
// State to store our value
const [storedValue, setStoredValue] = useState(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
console.error(error);
return initialValue;
}
});
// Return a wrapped version of useState's setter function
const setValue = (value) => {
try {
// Allow value to be a function (same API as useState)
const valueToStore = value instanceof Function ? value(storedValue) : value;
setStoredValue(valueToStore);
window.localStorage.setItem(key, JSON.stringify(valueToStore));
} catch (error) {
console.error(error);
}
};
return [storedValue, setValue];
}
// Usage
function App() {
const [theme, setTheme] = useLocalStorage('theme', 'light');
return (
<div className={theme}>
<button onClick={() => setTheme('dark')}>Dark</button>
<button onClick={() => setTheme('light')}>Light</button>
</div>
);
}
function useDebounce(value, delay) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => {
clearTimeout(handler);
};
}, [value, delay]);
return debouncedValue;
}
// Usage
function SearchInput() {
const [searchTerm, setSearchTerm] = useState('');
const debouncedSearchTerm = useDebounce(searchTerm, 500);
useEffect(() => {
if (debouncedSearchTerm) {
// Perform search with debounced value
fetch(`/api/search?q=${debouncedSearchTerm}`)
.then(/* handle response */);
}
}, [debouncedSearchTerm]);
return (
<input
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
placeholder="Search..."
/>
);
}
// Bad: Missing dependency
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetchUser(userId).then(setUser);
}, []); // userId should be in dependencies!
// ...
}
// Good: Include all dependencies
useEffect(() => {
fetchUser(userId).then(setUser);
}, [userId]);
// Bad: Creates infinite loop
function BadComponent() {
const [count, setCount] = useState(0);
useEffect(() => {
setCount(count + 1); // Changes count, triggers re-render
}, [count]); // Depends on count, so runs again!
// ...
}
// Good: Use functional update or remove dependency
useEffect(() => {
setCount(c => c + 1); // Functional update
}, []); // Or omit dependency if appropriate
// Bad: Unnecessary memoization
function SimpleComponent({ name }) {
const greeting = useMemo(() => `Hello ${name}`, [name]); // Overkill!
return <div>{greeting}</div>;
}
// Good: Just compute directly
function SimpleComponent({ name }) {
const greeting = `Hello ${name}`; // Simple computation, no memo needed
return <div>{greeting}</div>;
}
Only call Hooks at the top level
Only call Hooks from React functions
Custom Hooks must start with "use"
// Good use case: Expensive computation
function ExpensiveComponent({ items }) {
const processedItems = useMemo(() => {
return items
.filter(item => item.active)
.map(item => complexTransformation(item))
.sort((a, b) => a.score - b.score);
}, [items]);
return <div>{processedItems.map(renderItem)}</div>;
}
// Good use case: Passing callbacks to optimized child components
function ParentComponent() {
const [count, setCount] = useState(0);
// Without useCallback, new function on every render
const handleClick = useCallback(() => {
setCount(c => c + 1);
}, []); // Stable function reference
return <ExpensiveChild onClick={handleClick} />;
}
// Child wrapped in React.memo
const ExpensiveChild = React.memo(({ onClick }) => {
console.log('Rendering ExpensiveChild');
return <button onClick={onClick}>Click</button>;
});
After mastering hooks, progress to:
Hook Problem?
├── useEffect Issues?
│ ├── Infinite loop?
│ │ └── Check: Dependency array values
│ ├── Stale closure?
│ │ └── Fix: Use functional updates or refs
│ ├── Missing cleanup?
│ │ └── Fix: Return cleanup function
│ └── Race condition?
│ └── Fix: Use AbortController or cancelled flag
├── useState Issues?
│ ├── State not updating?
│ │ └── Check: Are you mutating state?
│ ├── Batching confusion?
│ │ └── Use: Functional updates
│ └── Initial value wrong?
│ └── Use: Lazy initialization
├── useContext Issues?
│ ├── undefined value?
│ │ └── Check: Provider hierarchy
│ └── Too many re-renders?
│ └── Fix: Split contexts
└── Custom Hook Issues?
├── Rules of Hooks violation?
│ └── Check: Conditional calls
└── Testing failures?
└── Use: renderHook from RTL
eslint-plugin-react-hooks| Warning/Error | Root Cause | Solution |
|---|---|---|
React Hook useEffect has missing dependencies | Incomplete deps array | Add missing deps or use ESLint disable with comment |
Cannot update a component while rendering | setState in render | Move to useEffect |
Rendered more hooks than during previous render | Conditional hook call | Ensure hooks always called in same order |
Custom hook name must start with "use" | Naming convention | Rename to useXxx |
AbortController for Race Conditions:
useEffect(() => {
const controller = new AbortController();
async function fetchData() {
try {
const res = await fetch(url, { signal: controller.signal });
const data = await res.json();
setData(data);
} catch (err) {
if (err.name !== 'AbortError') {
setError(err);
}
}
}
fetchData();
return () => controller.abort();
}, [url]);
Retry with Exponential Backoff:
function useRetryFetch(url, maxRetries = 3) {
const [state, setState] = useState({ data: null, loading: true, error: null });
useEffect(() => {
let cancelled = false;
let retryCount = 0;
async function fetchWithRetry() {
while (retryCount < maxRetries && !cancelled) {
try {
const res = await fetch(url);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
if (!cancelled) setState({ data, loading: false, error: null });
return;
} catch (err) {
retryCount++;
if (retryCount < maxRetries) {
await new Promise(r => setTimeout(r, Math.pow(2, retryCount) * 1000));
} else if (!cancelled) {
setState({ data: null, loading: false, error: err });
}
}
}
}
fetchWithRetry();
return () => { cancelled = true; };
}, [url, maxRetries]);
return state;
}
Version: 2.0.0 Last Updated: 2025-12-30 SASMP Version: 2.0.0 Specialization: React Hooks & Patterns Difficulty: Intermediate Estimated Learning Time: 4 weeks Changelog: Production-grade update with retry logic, circuit breaker, and comprehensive troubleshooting
Use this agent to verify that a Python Agent SDK application is properly configured, follows SDK best practices and documentation recommendations, and is ready for deployment or testing. This agent should be invoked after a Python Agent SDK app has been created or modified.
Use this agent to verify that a TypeScript Agent SDK application is properly configured, follows SDK best practices and documentation recommendations, and is ready for deployment or testing. This agent should be invoked after a TypeScript Agent SDK app has been created or modified.