React Mastery
Overview
Comprehensive reference for advanced React development: hooks internals, state management architectures, component patterns, security hardening, and performance + testing strategies. Use this skill when building production-grade React apps, debugging complex rendering, or architecting scalable frontend systems.
1. Hooks Deep Dive
Rules of Hooks
Only call hooks at the top level — never inside loops, conditions, or nested functions.
Only call hooks from React function components or custom hooks.
React relies on call order to associate state with hook slots; violating order breaks internal linked-list tracking.
// ❌ WRONG — conditional hook
if (isLoggedIn) {
const [user, setUser] = useState(null);
}
// ✅ CORRECT — always call, conditionally use const [user, setUser] = useState(null); if (!isLoggedIn) return null;
useEffect Lifecycle Patterns
// Mount only
useEffect(() => { initAnalytics(); }, []);
// Sync with dependency useEffect(() => { document.title = Count: ${count}; }, [count]);
// Cleanup (unmount + re-run) useEffect(() => { const controller = new AbortController(); fetch(/api/data, { signal: controller.signal }) .then(r => r.json()).then(setData); return () => controller.abort(); }, [query]);
// Subscription pattern useEffect(() => { const sub = eventBus.subscribe('refresh', handleRefresh); return () => sub.unsubscribe(); }, [handleRefresh]);
useReducer for Complex State
const initialState = { items: [], loading: false, error: null };
function reducer(state, action) { switch (action.type) { case 'FETCH_START': return { ...state, loading: true, error: null }; case 'FETCH_SUCCESS': return { ...state, loading: false, items: action.payload }; case 'FETCH_ERROR': return { ...state, loading: false, error: action.payload }; case 'ADD_ITEM': return { ...state, items: [...state.items, action.payload] }; case 'REMOVE_ITEM': return { ...state, items: state.items.filter(i => i.id !== action.payload) }; default: return state; } }
function ItemList() { const [state, dispatch] = useReducer(reducer, initialState); }
useMemo & useCallback
// Memoize expensive computation
const sortedItems = useMemo(
() => [...items].sort((a, b) => a.priority - b.priority), [items]
);
// Memoize callback reference for child component const handleClick = useCallback((id) => { setSelected(id); }, []);
Custom Hooks
function useDebounce(value, delay = 300) {
const [debounced, setDebounced] = useState(value);
useEffect(() => {
const timer = setTimeout(() => setDebounced(value), delay);
return () => clearTimeout(timer);
}, [value, delay]);
return debounced;
}
function useAsync(asyncFn, deps = []) { const [state, dispatch] = useReducer( (s, a) => ({ ...s, ...a }), { data: null, loading: true, error: null } ); useEffect(() => { let cancelled = false; dispatch({ loading: true, error: null }); asyncFn() .then(data => { if (!cancelled) dispatch({ data, loading: false }); }) .catch(err => { if (!cancelled) dispatch({ error: err, loading: false }); }); return () => { cancelled = true; }; }, deps); return state; }
2. State Management
Architecture Decision Flow
graph TD
A[State Need] --> B{Scope?}
B -->|Component-local| C[useState / useReducer]
B -->|Shared across subtree| D[Context API]
B -->|Global + frequent updates| E{Complexity?}
E -->|Medium| F[Zustand / Jotai]
E -->|High + middleware| G[Redux Toolkit]
A --> H{Server data?}
H -->|Yes| I[TanStack Query]
H -->|No| B
Context API
const ThemeCtx = createContext({ theme: 'light', toggle: () => {} });
function ThemeProvider({ children }) { const [theme, setTheme] = useState('light'); const toggle = useCallback(() => setTheme(t => t === 'light' ? 'dark' : 'light'), []); const value = useMemo(() => ({ theme, toggle }), [theme, toggle]); return {children} ; }
Zustand — Minimal Global Store
import { create } from 'zustand';
import { devtools, persist } from 'zustand/middleware';
const useStore = create(devtools(persist((set, get) => ({ cart: [], addToCart: (item) => set(s => ({ cart: [...s.cart, item] })), removeFromCart: (id) => set(s => ({ cart: s.cart.filter(i => i.id !== id) })), total: () => get().cart.reduce((sum, i) => sum + i.price, 0), }), { name: 'cart-store' })));
// Selector prevents unnecessary re-renders const items = useStore(s => s.cart);
Jotai — Atomic State
import { atom, useAtom, useAtomValue } from 'jotai';
const userAtom = atom(null); const isLoggedInAtom = atom((get) => get(userAtom) !== null);
function Profile() { const [user, setUser] = useAtom(userAtom); const loggedIn = useAtomValue(isLoggedInAtom); if (!loggedIn) return ; return
{user.name}
; }
import { createSlice, configureStore, createAsyncThunk } from '@reduxjs/toolkit';
const fetchUsers = createAsyncThunk('users/fetch', async () => { const res = await fetch('/api/users'); return res.json(); });
const usersSlice = createSlice({ name: 'users', initialState: { list: [], status: 'idle', error: null }, reducers: { addUser: (s, a) => { s.list.push(a.payload); } }, extraReducers: (b) => { b.addCase(fetchUsers.pending, s => { s.status = 'loading'; }) .addCase(fetchUsers.fulfilled, (s, a) => { s.list = a.payload; s.status = 'succeeded'; }) .addCase(fetchUsers.rejected, (s, a) => { s.status = 'failed'; s.error = a.error.message; }); }, });
const store = configureStore({ reducer: { users: usersSlice.reducer } });
TanStack Query — Server State
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
function useUsers() { return useQuery({ queryKey: ['users'], queryFn: () => fetch('/api/users').then(r => r.json()), staleTime: 5 * 60_000, retry: 2, }); }
function useAddUser() { const qc = useQueryClient(); return useMutation({ mutationFn: (u) => fetch('/api/users', { method: 'POST', body: JSON.stringify(u) }), onSuccess: () => qc.invalidateQueries({ queryKey: ['users'] }), }); }
3. Component Patterns
Component Hierarchy
graph TD
App --> ThemeProvider
ThemeProvider --> Layout
Layout --> Header
Layout --> ErrorBoundary
ErrorBoundary --> PageContent
PageContent --> DataProvider
DataProvider --> List
List --> ListItem
ListItem --> WithTooltip
Compound Components
function Tabs({ children, defaultIndex = 0 }) {
const [activeIndex, setActiveIndex] = useState(defaultIndex);
const ctx = useMemo(() => ({ activeIndex, setActiveIndex }), [activeIndex]);
return {children} ;
}
Tabs.List = function TabList({ children }) { const { activeIndex, setActiveIndex } = useContext(TabsContext); return (
{Children.map(children, (child, i) => cloneElement(child, { isActive: i === activeIndex, onClick: () => setActiveIndex(i) }) )}
); };
Tabs.Panel = function TabPanel({ children, index }) { const { activeIndex } = useContext(TabsContext); return activeIndex === index ?
{children}
: null; };
Render Props
function MouseTracker({ render }) {
const [pos, setPos] = useState({ x: 0, y: 0 });
useEffect(() => {
const handler = (e) => setPos({ x: e.clientX, y: e.clientY });
window.addEventListener('mousemove', handler);
return () => window.removeEventListener('mousemove', handler);
}, []);
return render(pos);
}
// Usage: } />
Higher-Order Components (HOCs)
function withAuth(WrappedComponent) {
return function AuthenticatedComponent(props) {
const { user, loading } = useAuth();
if (loading) return ;
if (!user) return ;
return ;
};
}
// Usage: export default withAuth(Dashboard);
Controlled vs Uncontrolled
// Controlled — React owns the truth
const [value, setValue] = useState('');
setValue(e.target.value)} />;
// Uncontrolled — DOM owns the truth const ref = useRef(); ; // Read: ref.current.value
Error Boundaries
class ErrorBoundary extends React.Component {
state = { hasError: false, error: null };
static getDerivedStateFromError(error) { return { hasError: true, error }; }
componentDidCatch(error, info) { logErrorToService(error, info.componentStack); }
render() {
return this.state.hasError
? (this.props.fallback ?? Something went wrong. )
: this.props.children;
}
}
4. React Security
XSS in JSX & dangerouslySetInnerHTML
JSX auto-escapes {userInput}. The real risk is dangerouslySetInnerHTML and href injection:
// ❌ VULNERABLE
Click
// ✅ SANITIZE import DOMPurify from 'dompurify';
// ✅ VALIDATE PROTOCOL const SAFE_URL = /^(https?:|mailto:|tel:)/i; function SafeLink({ href, ...props }) { if (!SAFE_URL.test(href)) return Invalid Link ; return ; }
Prototype Pollution
const DANGEROUS_KEYS = ['__proto__', 'constructor', 'prototype'];
function safeMerge(target, source) {
const clean = Object.entries(source)
.filter(([k]) => !DANGEROUS_KEYS.includes(k))
.reduce((o, [k, v]) => ({ ...o, [k]: v }), {});
return { ...target, ...clean };
}
Auth & Token Management
function useAuth() {
const [token, setToken] = useState(() => localStorage.getItem('token'));
const login = async (creds) => {
const { token } = await fetch('/api/login', {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(creds),
}).then(r => r.json());
localStorage.setItem('token', token);
setToken(token);
};
const logout = () => { localStorage.removeItem('token'); setToken(null); };
return { token, login, logout, isAuthenticated: !!token };
}
CSRF Protection
// Axios interceptor for CSRF
axios.interceptors.request.use((config) => {
if (['post', 'put', 'patch', 'delete'].includes(config.method)) {
config.headers['X-CSRF-Token'] = getCSRFToken();
}
return config;
});
Render Cycle & Optimization
sequenceDiagram
participant Parent
participant Child
participant DOM
Parent->>Parent: setState / dispatch
Parent->>Child: Re-render (props change?)
Note over Child: React.memo checks shallow equality
alt Props unchanged
Child-->>Parent: Skip render (memo hit)
else Props changed
Child->>Child: Render VDOM
Child->>DOM: Commit minimal diff
end
React.memo & Optimization
const ExpensiveList = React.memo(function ExpensiveList({ items, onSelect }) {
return items.map(item => );
}, (prev, next) => prev.items === next.items && prev.onSelect === next.onSelect);
Code Splitting & Suspense
const AdminPanel = React.lazy(() => import('./AdminPanel'));
function App() { return ( }> } /> ); }
// Data fetching with Suspense function Profile() { const { data } = useSuspenseQuery({ queryKey: ['profile'], queryFn: fetchProfile }); return
{data.name}
; }
Jest Unit Tests
describe('formatCurrency', () => {
it('formats USD', () => expect(formatCurrency(1234.5)).toBe('$1,234.50'));
it('handles zero', () => expect(formatCurrency(0)).toBe('$0.00'));
it('handles negatives', () => expect(formatCurrency(-10)).toBe('-$10.00'));
});
React Testing Library
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import LoginForm from './LoginForm';
test('submits credentials and shows success', async () => { const onSuccess = jest.fn(); render( ); const user = userEvent.setup(); await user.type(screen.getByLabelText(/email/i), 'user@test.com'); await user.type(screen.getByLabelText(/password/i), 'secret123'); await user.click(screen.getByRole('button', { name: /sign in/i })); await waitFor(() => expect(onSuccess).toHaveBeenCalledWith({ email: 'user@test.com', password: 'secret123', })); });
Playwright E2E
test('user can log in and see dashboard', async ({ page }) => {
await page.goto('/login');
await page.fill('[data-testid="email"]', 'admin@test.com');
await page.fill('[data-testid="password"]', 'admin123');
await page.click('button[type="submit"]');
await expect(page).toHaveURL('/dashboard');
await expect(page.locator('h1')).toContainText('Welcome, Admin');
});
Quick Reference Cheatsheet
| Topic | Key API | When to Use | |-------|---------|-------------| | Local state | useState / useReducer | Component-scoped | | Shared subtree | Context API | Theme, locale, auth | | Global store | Zustand / Jotai | Medium-complexity global | | Enterprise store | Redux Toolkit | Middleware, devtools, large teams | | Server cache | TanStack Query | API data, pagination, mutations | | Memoization | useMemo / useCallback | Expensive calc or stable ref | | Compound comps | Context + cloneElement | API | | Error handling | ErrorBoundary | Catch render crashes | | Security | DOMPurify, CSRF tokens | User HTML, state-changing requests | | Perf | React.memo, lazy, Suspense | Prevent re-renders, reduce bundle |
Anti-Patterns
Prop drilling >3 levels → use Context or state library
Huge useEffect deps → split into focused effects or useReducer
Stale closures in setInterval → use useRef for mutable latest value
Inline object/array props → causes re-renders; extract to useMemo
Index as key → use stable unique IDs
Uncaught promise rejections → always .catch() or error boundary
Direct DOM manipulation → let React own the DOM; use refs sparingly
dangerouslySetInnerHTML without sanitization → always use DOMPurify