React
React hooks, component patterns, state management, performance optimisation, and best practices.
Core Hooks
3 topics
useState & useReducer
import { useState, useReducer } from "react";
// useState
const [count, setCount] = useState(0);
const [user, setUser] = useState<User | null>(null);
// Functional update (safe for async)
setCount(prev => prev + 1);
// useReducer for complex state
type Action = { type: "inc" } | { type: "dec" } | { type: "reset" };
function reducer(state: number, action: Action): number {
switch (action.type) {
case "inc": return state + 1;
case "dec": return state - 1;
case "reset": return 0;
}
}
const [count, dispatch] = useReducer(reducer, 0);
dispatch({ type: "inc" });💡 Use functional updates when new state depends on old state
⚡ useReducer shines when state has multiple sub-values or complex transitions
useEffect
import { useEffect, useState } from "react";
// Run after every render
useEffect(() => { document.title = `Count: ${count}`; });
// Run once on mount
useEffect(() => {
fetchUser(id).then(setUser);
}, []);
// Run when deps change
useEffect(() => {
const sub = subscribe(userId);
return () => sub.unsubscribe(); // Cleanup!
}, [userId]);
// ✅ Better: use async function inside
useEffect(() => {
async function load() {
const data = await fetchUser(id);
setUser(data);
}
load();
}, [id]);⚠️ Always return a cleanup function for subscriptions/timers
💡 Never make the useEffect callback async — define async inside it
useMemo & useCallback
import { useMemo, useCallback } from "react";
// useMemo: memoize expensive computation
const sorted = useMemo(
() => [...items].sort((a, b) => a.name.localeCompare(b.name)),
[items]
);
// useCallback: stable function reference
const handleClick = useCallback(
(id: number) => dispatch({ type: "remove", id }),
[dispatch]
);
// ✅ Use when passing to memoized child
const Child = React.memo(({ onClick }) => (
<button onClick={onClick}>Click</button>
));💡 Only memoize when you have a measured performance problem
⚠️ Over-memoizing adds overhead — profile first
Core Components and Props
3 topics
Create Reusable Function Components
Build UI pieces as small, reusable function components. Pass configuration through props so components stay flexible and testable.
function Badge({ label, tone = 'info' }) {
return <span className={`badge badge--${tone}`}>{label}</span>;
}
export default function UserCard({ user }) {
return (
<article className="card">
<h3>{user.name}</h3>
<p>{user.email}</p>
<Badge label={user.role} tone={user.role === 'Admin' ? 'success' : 'info'} />
</article>
);
}Use default values in destructuring for optional props.
Keep components focused on one UI responsibility.
Render Lists with Stable Keys
When rendering arrays, always use stable unique keys (like IDs) to avoid UI glitches and unnecessary re-renders.
function TodoList({ todos, onToggle }) {
return (
<ul>
{todos.map((todo) => (
<li key={todo.id}>
<label>
<input
type="checkbox"
checked={todo.done}
onChange={() => onToggle(todo.id)}
/>
{todo.title}
</label>
</li>
))}
</ul>
);
}Never use array index as key for dynamic/reorderable lists.
Keys should come from your data model.
Conditional UI for Loading and Empty States
Show explicit loading, empty, and error states so users understand what is happening in data-driven views.
function OrdersPanel({ isLoading, error, orders }) {
if (isLoading) return <p>Loading orders…</p>;
if (error) return <p role="alert">Failed to load orders.</p>;
if (orders.length === 0) return <p>No orders yet.</p>;
return (
<ul>
{orders.map((o) => (
<li key={o.id}>#{o.id} — {o.status}</li>
))}
</ul>
);
}Return early for cleaner render logic.
Always include empty-state UX in dashboards and tables.
Component Patterns
2 topics
Custom Hooks
import { useState, useEffect } from "react";
function useFetch<T>(url: string) {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
let cancelled = false;
setLoading(true);
fetch(url)
.then(r => r.json())
.then(d => { if (!cancelled) setData(d); })
.catch(e => { if (!cancelled) setError(e); })
.finally(() => { if (!cancelled) setLoading(false); });
return () => { cancelled = true; };
}, [url]);
return { data, loading, error };
}
// Usage
const { data: user, loading } = useFetch<User>(`/api/users/${id}`);💡 Extract reusable logic into custom hooks prefixed with 'use'
⚡ The cancelled flag prevents state updates on unmounted components
Context API
import { createContext, useContext, useState } from "react";
interface ThemeCtx { theme: string; toggle: () => void; }
const ThemeContext = createContext<ThemeCtx | null>(null);
export function ThemeProvider({ children }: { children: React.ReactNode }) {
const [theme, setTheme] = useState("light");
const toggle = () => setTheme(t => t === "light" ? "dark" : "light");
return (
<ThemeContext.Provider value={{ theme, toggle }}>
{children}
</ThemeContext.Provider>
);
}
export function useTheme() {
const ctx = useContext(ThemeContext);
if (!ctx) throw new Error("useTheme must be inside ThemeProvider");
return ctx;
}💡 Throw in the hook if context is null — catches missing Provider early
⚡ Split contexts by update frequency to avoid unnecessary re-renders
State and Hooks
3 topics
Manage Local State with useState
Use useState for component-local data like toggles, inputs, and simple objects.
import { useState } from 'react';
export default function QuantitySelector() {
const [qty, setQty] = useState(1);
return (
<div>
<button onClick={() => setQty((q) => Math.max(1, q - 1))}>-</button>
<span>{qty}</span>
<button onClick={() => setQty((q) => q + 1)}>+</button>
</div>
);
}Use functional updates when next state depends on previous state.
Keep state minimal; derive values when possible.
Handle Side Effects with useEffect
Use effects for syncing with external systems (APIs, timers, subscriptions), not for pure calculations.
import { useEffect, useState } from 'react';
export default function ProductList() {
const [products, setProducts] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
let ignore = false;
async function load() {
setLoading(true);
const res = await fetch('/api/products');
const data = await res.json();
if (!ignore) setProducts(data);
if (!ignore) setLoading(false);
}
load();
return () => { ignore = true; };
}, []);
if (loading) return <p>Loading…</p>;
return <pre>{JSON.stringify(products, null, 2)}</pre>;
}Add cleanup logic to avoid setting state on unmounted components.
Prefer framework/data-layer fetching when available.
Share Logic with Custom Hooks
Extract repeated stateful logic into custom hooks to keep components lean and consistent across pages.
import { useEffect, useState } from 'react';
function useOnlineStatus() {
const [isOnline, setIsOnline] = useState(navigator.onLine);
useEffect(() => {
const onOnline = () => setIsOnline(true);
const onOffline = () => setIsOnline(false);
window.addEventListener('online', onOnline);
window.addEventListener('offline', onOffline);
return () => {
window.removeEventListener('online', onOnline);
window.removeEventListener('offline', onOffline);
};
}, []);
return isOnline;
}
export default function StatusBanner() {
const isOnline = useOnlineStatus();
return <p>{isOnline ? '🟢 Online' : '🔴 Offline'}</p>;
}Prefix custom hooks with use.
Custom hooks can return values, functions, or objects.
Forms and User Input
2 topics
Controlled Form Inputs
Use controlled components for predictable input handling, validation, and submission.
import { useState } from 'react';
export default function SignupForm() {
const [form, setForm] = useState({ email: '', password: '' });
function onChange(e) {
const { name, value } = e.target;
setForm((f) => ({ ...f, [name]: value }));
}
function onSubmit(e) {
e.preventDefault();
console.log('submit', form);
}
return (
<form onSubmit={onSubmit}>
<input name="email" value={form.email} onChange={onChange} placeholder="Email" />
<input name="password" type="password" value={form.password} onChange={onChange} placeholder="Password" />
<button type="submit">Create account</button>
</form>
);
}Use one change handler for multiple fields via name attribute.
Prevent default form submission in SPA flows.
Client-Side Validation Before Submit
Validate required fields and constraints before API calls to reduce failed requests and improve UX.
import { useState } from 'react';
export default function ProfileForm() {
const [name, setName] = useState('');
const [error, setError] = useState('');
function handleSubmit(e) {
e.preventDefault();
if (name.trim().length < 2) {
setError('Name must be at least 2 characters');
return;
}
setError('');
// await fetch('/api/profile', { method: 'POST', body: JSON.stringify({ name }) })
}
return (
<form onSubmit={handleSubmit}>
<input value={name} onChange={(e) => setName(e.target.value)} />
{error && <p role="alert">{error}</p>}
<button>Save</button>
</form>
);
}Show field-specific messages near the input.
Combine client validation with server-side validation.
Routing and App Structure
2 topics
Set Up Routes with React Router
Organize SPA pages with nested routes, layouts, and parameterized paths.
import { BrowserRouter, Routes, Route, Link, useParams } from 'react-router-dom';
function Layout() {
return (
<>
<nav>
<Link to="/">Home</Link> | <Link to="/products/42">Product</Link>
</nav>
<hr />
</>
);
}
function ProductPage() {
const { productId } = useParams();
return <h2>Product #{productId}</h2>;
}
export default function App() {
return (
<BrowserRouter>
<Layout />
<Routes>
<Route path="/" element={<h1>Home</h1>} />
<Route path="/products/:productId" element={<ProductPage />} />
</Routes>
</BrowserRouter>
);
}Use route params for entity detail pages.
Keep persistent nav and shell in layout components.
Feature-Based Folder Structure
Group files by feature (orders, auth, dashboard) rather than file type to improve scalability in teams.
src/
app/
router.jsx
providers.jsx
features/
auth/
api.js
AuthPage.jsx
authSlice.js
orders/
api.js
OrdersPage.jsx
OrderRow.jsx
useOrders.js
shared/
components/
Button.jsx
Modal.jsx
utils/
formatCurrency.jsStart simple and refactor into features as app grows.
Keep shared modules truly generic to avoid coupling.
Performance and Production Patterns
3 topics
Prevent Expensive Recomputations with useMemo
Memoize heavy calculations so they only rerun when dependencies change.
import { useMemo, useState } from 'react';
function filterProducts(products, query) {
console.log('Filtering...');
return products.filter((p) => p.name.toLowerCase().includes(query.toLowerCase()));
}
export default function SearchableProducts({ products }) {
const [query, setQuery] = useState('');
const filtered = useMemo(() => filterProducts(products, query), [products, query]);
return (
<>
<input value={query} onChange={(e) => setQuery(e.target.value)} placeholder="Search" />
<p>{filtered.length} results</p>
</>
);
}Use useMemo only for measurable expensive work.
Avoid premature optimization.
Split Code with React.lazy and Suspense
Load large pages on demand to reduce initial bundle size and improve first load performance.
import { Suspense, lazy } from 'react';
import { Routes, Route } from 'react-router-dom';
const ReportsPage = lazy(() => import('./ReportsPage'));
export default function AppRoutes() {
return (
<Suspense fallback={<p>Loading page…</p>}>
<Routes>
<Route path="/reports" element={<ReportsPage />} />
</Routes>
</Suspense>
);
}Lazy-load route-level components first for biggest impact.
Use meaningful fallback UI (skeletons/spinners).
Error Boundaries for Safer Production UIs
Catch rendering errors in subtree components and show fallback UI instead of a full app crash.
import React from 'react';
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError() {
return { hasError: true };
}
componentDidCatch(error, info) {
console.error('UI error:', error, info);
// Send to Sentry/Datadog/etc.
}
render() {
if (this.state.hasError) return <h2>Something went wrong.</h2>;
return this.props.children;
}
}
export default ErrorBoundary;Wrap unstable or third-party-heavy sections with boundaries.
Log errors to monitoring tools for triage.
Related Cheat Sheets
More hands-on references connected to this topic.
Next.js App Router, server components, data fetching, routing, metadata, and deployment patterns.
shared topics, same difficulty
same difficulty, keyword overlap
FastAPI routes, request models, dependency injection, authentication, and async patterns.
same difficulty, keyword overlap
Redis data types, caching patterns, pub/sub, streams, and common use cases.
same difficulty, keyword overlap
Related Articles
Background reading and deeper explanations for this sheet.
Intro to FastAPI: Build a Production-Ready Python API with Real Examples
FastAPI is one of the fastest ways to build modern APIs in Python without sacrificing code quality or developer experience. In this practical, beginner-friendly guide, you’ll learn how to create endpoints, validate data, handle errors, connect a database, secure routes, and prepare your FastAPI app for real-world deployment.
keyword overlap
GenAI SaaS Architecture: A Practical Blueprint for Building, Scaling, and Securing AI Products
Designing a SaaS product on top of GenAI is more than calling an LLM API—it requires thoughtful architecture across product, data, safety, and operations. This practical guide walks you through a beginner-friendly yet deep system blueprint, with real-world patterns, code snippets, and deployment strategies you can use immediately.
keyword overlap
Scaling AI APIs with FastAPI, Docker, and Load Balancers: A Practical Guide for Production
Building an AI API is easy—keeping it fast and reliable under real traffic is the hard part. In this practical guide, you’ll learn how to scale FastAPI services with Docker and load balancers, from local setup to production architecture, with real-world examples, code, and deployment tips.
keyword overlap
Event-Driven Agents with Kafka Streams: A Practical Guide for Building Real-Time AI Workflows
Event-driven agents let you move from slow, request/response automation to responsive systems that react in milliseconds to real-world signals. In this hands-on guide, you’ll learn how to design, build, and operate agentic workflows using Apache Kafka and Kafka Streams, with practical patterns, code snippets, and production-ready advice.
keyword overlap