React Cheatsheet
A dense, copy-paste-ready reference covering every major React concept. Jump to the section you need — each code block is self-contained and immediately usable.
1. Component Basics
Function component
// Minimal function component
function Greeting() {
return <h1>Hello, world!</h1>
}
// With props (destructured)
function Welcome({ name, age }: { name: string; age: number }) {
return <p>{name} is {age} years old.</p>
}
// Default props via default parameter values
function Button({ label = 'Click me', disabled = false }) {
return <button disabled={disabled}>{label}</button>
}
// Fragment — avoids an extra DOM wrapper
function Pair() {
return (
<>
<p>First</p>
<p>Second</p>
</>
)
}
// children prop
function Card({ children }: { children: React.ReactNode }) {
return <div className="card">{children}</div>
}
export default Greeting2. JSX Essentials
JSX quick reference
// Expressions go in curly braces
const name = 'Alice'
const el = <h1>Hello, {name}!</h1>
// className instead of class
<div className="container active">...</div>
// Inline styles — camelCase keys, value is a string or number
<p style={{ color: 'red', fontSize: 16 }}>Styled text</p>
// Conditional rendering
{isLoggedIn && <Dashboard />}
{isLoggedIn ? <Dashboard /> : <Login />}
// Rendering a list — always provide a stable key
const items = ['Apple', 'Banana', 'Cherry']
<ul>
{items.map((item) => (
<li key={item}>{item}</li>
))}
</ul>
// Rendering objects — spread props
const inputProps = { type: 'text', placeholder: 'Search…' }
<input {...inputProps} />3. Hooks Quick Reference
Hook | Purpose | Signature |
|---|---|---|
| Local component state |
|
| Side effects & subscriptions |
|
| Read a React context |
|
| Complex state logic |
|
| Mutable ref / DOM node |
|
| Memoize expensive value |
|
| Memoize function reference |
|
| Stable unique ID (a11y) |
|
| Mark state update as non-urgent |
|
| Defer re-render of slow tree |
|
| Sync effect after DOM paint |
|
| Customize ref exposed to parent |
|
4. useState Patterns
useState — boolean, object, array
import { useState } from 'react'
// --- Boolean toggle ---
const [isOpen, setIsOpen] = useState(false)
const toggle = () => setIsOpen((prev) => !prev)
// --- Object state — always spread to preserve other fields ---
const [user, setUser] = useState({ name: '', email: '' })
const updateName = (name: string) =>
setUser((prev) => ({ ...prev, name }))
// --- Array — add item ---
const [items, setItems] = useState<string[]>([])
const addItem = (item: string) =>
setItems((prev) => [...prev, item])
// --- Array — remove item by index ---
const removeItem = (index: number) =>
setItems((prev) => prev.filter((_, i) => i !== index))
// --- Array — update item at index ---
const updateItem = (index: number, next: string) =>
setItems((prev) =>
prev.map((item, i) => (i === index ? next : item))
)
// --- Lazy initial state (expensive computation runs once) ---
const [count, setCount] = useState(() => {
const saved = localStorage.getItem('count')
return saved ? Number(saved) : 0
})5. useEffect Patterns
useEffect — all common patterns
import { useEffect, useState } from 'react'
// --- Run once on mount (empty deps array) ---
useEffect(() => {
document.title = 'App loaded'
}, [])
// --- Run on every render (no deps array at all) ---
useEffect(() => {
console.log('rendered')
})
// --- Run when specific dep changes ---
useEffect(() => {
document.title = `Count: ${count}`
}, [count])
// --- Cleanup: returned function runs before next effect & on unmount ---
useEffect(() => {
const timer = setInterval(() => tick(), 1000)
return () => clearInterval(timer)
}, [])
// --- Fetch with AbortController (prevents setState after unmount) ---
useEffect(() => {
const controller = new AbortController()
async function load() {
try {
const res = await fetch('/api/data', { signal: controller.signal })
const json = await res.json()
setData(json)
} catch (err) {
if ((err as Error).name !== 'AbortError') setError(err as Error)
}
}
load()
return () => controller.abort()
}, [id]) // re-fetch when id changes
// --- Event listener cleanup ---
useEffect(() => {
const handleResize = () => setWidth(window.innerWidth)
window.addEventListener('resize', handleResize)
return () => window.removeEventListener('resize', handleResize)
}, [])6. useRef Patterns
useRef — DOM access, mutable value, previous value
import { useRef, useEffect, useState } from 'react'
// --- DOM ref: focus an input on mount ---
function AutoFocusInput() {
const inputRef = useRef<HTMLInputElement>(null)
useEffect(() => {
inputRef.current?.focus()
}, [])
return <input ref={inputRef} />
}
// --- Mutable value: track interval ID without triggering re-render ---
function Timer() {
const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null)
const start = () => {
intervalRef.current = setInterval(() => tick(), 1000)
}
const stop = () => {
if (intervalRef.current) clearInterval(intervalRef.current)
}
return <><button onClick={start}>Start</button><button onClick={stop}>Stop</button></>
}
// --- Previous value pattern ---
function usePrevious<T>(value: T): T | undefined {
const ref = useRef<T | undefined>(undefined)
useEffect(() => {
ref.current = value
})
return ref.current
}
// Usage: const prevCount = usePrevious(count)7. useReducer Pattern
useReducer — typed reducer
import { useReducer } from 'react'
type State = { count: number; step: number }
type Action =
| { type: 'increment' }
| { type: 'decrement' }
| { type: 'reset' }
| { type: 'setStep'; payload: number }
const initialState: State = { count: 0, step: 1 }
function reducer(state: State, action: Action): State {
switch (action.type) {
case 'increment':
return { ...state, count: state.count + state.step }
case 'decrement':
return { ...state, count: state.count - state.step }
case 'reset':
return initialState
case 'setStep':
return { ...state, step: action.payload }
default:
return state
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState)
return (
<>
<p>Count: {state.count} (step: {state.step})</p>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
<button onClick={() => dispatch({ type: 'reset' })}>Reset</button>
<input
type="number"
value={state.step}
onChange={(e) =>
dispatch({ type: 'setStep', payload: Number(e.target.value) })
}
/>
</>
)
}8. Context Pattern
createContext + Provider + useContext
import { createContext, useContext, useState, ReactNode } from 'react'
// 1. Define the shape
interface ThemeContextValue {
theme: 'light' | 'dark'
toggle: () => void
}
// 2. Create context with a sensible default (used when no Provider is above)
const ThemeContext = createContext<ThemeContextValue>({
theme: 'light',
toggle: () => {},
})
// 3. Provider component — owns the state
export function ThemeProvider({ children }: { children: ReactNode }) {
const [theme, setTheme] = useState<'light' | 'dark'>('light')
const toggle = () => setTheme((t) => (t === 'light' ? 'dark' : 'light'))
return (
<ThemeContext.Provider value={{ theme, toggle }}>
{children}
</ThemeContext.Provider>
)
}
// 4. Custom hook — encapsulates useContext + null guard
export function useTheme() {
return useContext(ThemeContext)
}
// 5. Consumer
function ThemeToggleButton() {
const { theme, toggle } = useTheme()
return (
<button onClick={toggle}>
Current theme: {theme}
</button>
)
}9. Custom Hook Template
Custom hook skeleton
import { useState, useEffect } from 'react'
// Convention: name starts with "use", returns values/actions
function useCustomHook(param: string) {
const [data, setData] = useState<string | null>(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState<Error | null>(null)
useEffect(() => {
let cancelled = false
setLoading(true)
someAsyncOperation(param)
.then((result) => {
if (!cancelled) setData(result)
})
.catch((err) => {
if (!cancelled) setError(err)
})
.finally(() => {
if (!cancelled) setLoading(false)
})
return () => { cancelled = true }
}, [param])
return { data, loading, error }
}useLocalStorage — practical custom hook
import { useState, useEffect } from 'react'
function useLocalStorage<T>(key: string, initialValue: T) {
const [storedValue, setStoredValue] = useState<T>(() => {
if (typeof window === 'undefined') return initialValue
try {
const item = window.localStorage.getItem(key)
return item ? (JSON.parse(item) as T) : initialValue
} catch {
return initialValue
}
})
const setValue = (value: T | ((prev: T) => T)) => {
try {
const next = value instanceof Function ? value(storedValue) : value
setStoredValue(next)
window.localStorage.setItem(key, JSON.stringify(next))
} catch (err) {
console.error(err)
}
}
return [storedValue, setValue] as const
}
// Usage:
// const [name, setName] = useLocalStorage('username', '')
// const [prefs, setPrefs] = useLocalStorage('prefs', { dark: false })10. React.memo + useMemo + useCallback
Memoization — all three patterns side by side
import { memo, useMemo, useCallback, useState } from 'react'
// --- React.memo: skip re-render when props haven't changed ---
const ExpensiveChild = memo(function ExpensiveChild({
value,
onClick,
}: {
value: number
onClick: () => void
}) {
console.log('ExpensiveChild rendered')
return <button onClick={onClick}>{value}</button>
})
// --- useMemo: memoize a derived value ---
function Parent({ items }: { items: number[] }) {
const [filter, setFilter] = useState(0)
// Only recomputes when items or filter changes
const filtered = useMemo(
() => items.filter((n) => n > filter),
[items, filter]
)
// --- useCallback: stable function reference so memo child doesn't re-render ---
const handleClick = useCallback(() => {
console.log('clicked')
}, []) // empty deps → same reference forever
return (
<>
<input
type="number"
value={filter}
onChange={(e) => setFilter(Number(e.target.value))}
/>
{filtered.map((n) => (
<ExpensiveChild key={n} value={n} onClick={handleClick} />
))}
</>
)
}11. Event Handlers
Event handler types and patterns
import { MouseEvent, ChangeEvent, FormEvent, KeyboardEvent } from 'react'
// onClick
function handleClick(e: MouseEvent<HTMLButtonElement>) {
e.preventDefault()
console.log('clicked at', e.clientX, e.clientY)
}
// onChange — input
function handleChange(e: ChangeEvent<HTMLInputElement>) {
setValue(e.target.value)
}
// onSubmit — form
function handleSubmit(e: FormEvent<HTMLFormElement>) {
e.preventDefault()
// read form data…
}
// onKeyDown
function handleKeyDown(e: KeyboardEvent<HTMLInputElement>) {
if (e.key === 'Enter') submit()
if (e.key === 'Escape') clear()
}
// Passing args to a handler (use a wrapper arrow function)
function ItemList({ items }: { items: string[] }) {
const handleDelete = (id: string) => () => {
deleteItem(id)
}
return (
<ul>
{items.map((item) => (
<li key={item}>
{item}
<button onClick={handleDelete(item)}>Delete</button>
</li>
))}
</ul>
)
}12. Form Patterns
Controlled input, multiple inputs, form submit
import { useState, ChangeEvent, FormEvent } from 'react'
// --- Single controlled input ---
function SearchBox() {
const [query, setQuery] = useState('')
return (
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search…"
/>
)
}
// --- Multiple inputs via name key ---
interface FormValues {
username: string
email: string
age: string
}
function SignupForm() {
const [form, setForm] = useState<FormValues>({
username: '',
email: '',
age: '',
})
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target
setForm((prev) => ({ ...prev, [name]: value }))
}
const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault()
console.log('submitted', form)
}
return (
<form onSubmit={handleSubmit}>
<input name="username" value={form.username} onChange={handleChange} placeholder="Username" />
<input name="email" value={form.email} onChange={handleChange} placeholder="Email" />
<input name="age" value={form.age} onChange={handleChange} placeholder="Age" type="number" />
<button type="submit">Sign up</button>
</form>
)
}
// --- Select, checkbox, textarea ---
<select value={selected} onChange={(e) => setSelected(e.target.value)}>
<option value="a">Option A</option>
<option value="b">Option B</option>
</select>
<input
type="checkbox"
checked={agreed}
onChange={(e) => setAgreed(e.target.checked)}
/>
<textarea value={bio} onChange={(e) => setBio(e.target.value)} />13. React Router v6
React Router v6 — setup and core hooks
import {
BrowserRouter,
Routes,
Route,
Link,
NavLink,
Outlet,
useParams,
useNavigate,
useSearchParams,
useLocation,
} from 'react-router-dom'
// --- App-level setup ---
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/users" element={<UsersLayout />}>
<Route index element={<UserList />} />
<Route path=":id" element={<UserDetail />} />
</Route>
<Route path="*" element={<NotFound />} />
</Routes>
</BrowserRouter>
)
}
// --- Nested layout with Outlet ---
function UsersLayout() {
return (
<div>
<nav>
<Link to="/users">All Users</Link>
</nav>
<Outlet /> {/* child routes render here */}
</div>
)
}
// --- useParams ---
function UserDetail() {
const { id } = useParams<{ id: string }>()
return <p>User ID: {id}</p>
}
// --- useNavigate ---
function LoginForm() {
const navigate = useNavigate()
const handleSubmit = () => {
// after login…
navigate('/dashboard', { replace: true })
// go back: navigate(-1)
}
return <button onClick={handleSubmit}>Login</button>
}
// --- useSearchParams (/products?sort=price&order=asc) ---
function ProductList() {
const [searchParams, setSearchParams] = useSearchParams()
const sort = searchParams.get('sort') ?? 'name'
return (
<button onClick={() => setSearchParams({ sort: 'price' })}>
Sort by price (current: {sort})
</button>
)
}
// --- NavLink — applies activeClassName automatically ---
<NavLink
to="/about"
className={({ isActive }) => (isActive ? 'active' : '')}
>
About
</NavLink>14. Error Boundary
Error boundary — class component template
import { Component, ReactNode, ErrorInfo } from 'react'
interface Props {
fallback?: ReactNode
children: ReactNode
}
interface State {
hasError: boolean
error: Error | null
}
class ErrorBoundary extends Component<Props, State> {
constructor(props: Props) {
super(props)
this.state = { hasError: false, error: null }
}
static getDerivedStateFromError(error: Error): State {
return { hasError: true, error }
}
componentDidCatch(error: Error, info: ErrorInfo) {
// Log to an error reporting service
console.error('ErrorBoundary caught:', error, info.componentStack)
}
render() {
if (this.state.hasError) {
return this.props.fallback ?? (
<div role="alert">
<h2>Something went wrong.</h2>
<details>
<summary>Details</summary>
<pre>{this.state.error?.message}</pre>
</details>
<button onClick={() => this.setState({ hasError: false, error: null })}>
Try again
</button>
</div>
)
}
return this.props.children
}
}
// Usage
function App() {
return (
<ErrorBoundary fallback={<p>Oops! Please reload the page.</p>}>
<MyWidget />
</ErrorBoundary>
)
}15. TypeScript Props
Typing props — interface, optional, children, events
import { ReactNode, MouseEvent, ChangeEvent, CSSProperties } from 'react'
// --- Basic interface ---
interface ButtonProps {
label: string // required string
variant?: 'primary' | 'secondary' // optional union
disabled?: boolean
onClick: (e: MouseEvent<HTMLButtonElement>) => void
}
// --- children ---
interface PanelProps {
title: string
children: ReactNode // anything renderable
}
// --- Generic component ---
interface ListProps<T> {
items: T[]
renderItem: (item: T, index: number) => ReactNode
keyExtractor: (item: T) => string
}
function GenericList<T>({ items, renderItem, keyExtractor }: ListProps<T>) {
return (
<ul>
{items.map((item, i) => (
<li key={keyExtractor(item)}>{renderItem(item, i)}</li>
))}
</ul>
)
}
// --- Common event types ---
type InputChangeHandler = (e: ChangeEvent<HTMLInputElement>) => void
type SelectChangeHandler = (e: ChangeEvent<HTMLSelectElement>) => void
type TextAreaChangeHandler = (e: ChangeEvent<HTMLTextAreaElement>) => void
// --- forwardRef with TypeScript ---
import { forwardRef } from 'react'
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
label: string
}
const Input = forwardRef<HTMLInputElement, InputProps>(
({ label, ...rest }, ref) => (
<label>
{label}
<input ref={ref} {...rest} />
</label>
)
)
Input.displayName = 'Input'16. Lazy Loading & Suspense
React.lazy + Suspense + code splitting
import { lazy, Suspense } from 'react'
// Lazy-load a component (dynamic import)
const Dashboard = lazy(() => import('./Dashboard'))
const Chart = lazy(() => import('./Chart'))
// Fallback renders while the lazy component loads
function App() {
return (
<Suspense fallback={<p>Loading…</p>}>
<Dashboard />
</Suspense>
)
}
// Multiple lazy components share one Suspense boundary
function Analytics() {
return (
<Suspense fallback={<Spinner />}>
<Chart type="bar" />
<Chart type="line" />
</Suspense>
)
}
// Nested Suspense boundaries for granular loading UI
function Page() {
return (
<>
<Suspense fallback={<HeaderSkeleton />}>
<Header />
</Suspense>
<Suspense fallback={<ContentSkeleton />}>
<MainContent />
</Suspense>
</>
)
}17. Performance Checklist
React.memo — wrap stable child components that receive the same props most renders
useCallback — stabilize function props passed to memoized children
useMemo — memoize expensive derived values (sorts, filters, transforms)
React.lazy + Suspense — split large components out of the initial bundle
Virtualize long lists — use
react-windowor@tanstack/react-virtualfor lists > 100 itemsKey stability — use stable IDs as keys, never array index for reorderable lists
Avoid context churn — split context values that change at different frequencies
State colocation — keep state as close to where it's used as possible
useTransition — wrap non-urgent state updates so they don't block user input
Profiler — use React DevTools Profiler to find the actual bottleneck before optimizing
18. Common Patterns
Conditional rendering patterns
// Short-circuit — renders nothing when condition is false
{isLoggedIn && <UserMenu />}
// Ternary — renders one of two branches
{isLoading ? <Spinner /> : <Content data={data} />}
// Early return null — useful inside a component
function Tooltip({ visible, text }: { visible: boolean; text: string }) {
if (!visible) return null
return <div className="tooltip">{text}</div>
}
// iife for complex conditions in JSX
{(() => {
if (status === 'loading') return <Spinner />
if (status === 'error') return <Error />
return <Data />
})()}
// Switch pattern via object map
const statusMap: Record<string, ReactNode> = {
loading: <Spinner />,
error: <ErrorMessage />,
success: <DataView />,
}
return statusMap[status] ?? nullList render patterns
// Basic list
{users.map((user) => (
<UserCard key={user.id} user={user} />
))}
// Empty state
{users.length === 0
? <p>No users found.</p>
: users.map((user) => <UserCard key={user.id} user={user} />)
}
// Grouped list
{Object.entries(groupedItems).map(([group, items]) => (
<section key={group}>
<h3>{group}</h3>
{items.map((item) => (
<Item key={item.id} item={item} />
))}
</section>
))}
// Paginated slice
{items.slice(0, page * pageSize).map((item) => (
<Item key={item.id} item={item} />
))}Portal — render outside the parent DOM tree
import { createPortal } from 'react-dom'
function Modal({ children, isOpen }: { children: ReactNode; isOpen: boolean }) {
if (!isOpen) return null
// Renders children into <div id="modal-root"> regardless of where
// Modal sits in the component tree
return createPortal(
<div className="modal-overlay">
<div className="modal-box">{children}</div>
</div>,
document.getElementById('modal-root')!
)
}
// index.html needs: <div id="modal-root"></div>Render props pattern
interface MouseTrackerProps {
render: (pos: { x: number; y: number }) => ReactNode
}
function MouseTracker({ render }: MouseTrackerProps) {
const [pos, setPos] = useState({ x: 0, y: 0 })
return (
<div
style={{ width: '100%', height: 300, border: '1px solid #ccc' }}
onMouseMove={(e) => setPos({ x: e.clientX, y: e.clientY })}
>
{render(pos)}
</div>
)
}
// Usage:
<MouseTracker
render={({ x, y }) => <p>Mouse at ({x}, {y})</p>}
/>Compound components pattern
import { createContext, useContext, useState, ReactNode } from 'react'
// Internal context — not exported
const TabsContext = createContext<{
active: string
setActive: (id: string) => void
} | null>(null)
function useTabsContext() {
const ctx = useContext(TabsContext)
if (!ctx) throw new Error('Must be used inside <Tabs>')
return ctx
}
// Parent
function Tabs({ defaultTab, children }: { defaultTab: string; children: ReactNode }) {
const [active, setActive] = useState(defaultTab)
return (
<TabsContext.Provider value={{ active, setActive }}>
<div className="tabs">{children}</div>
</TabsContext.Provider>
)
}
// Sub-components
function TabList({ children }: { children: ReactNode }) {
return <div role="tablist">{children}</div>
}
function Tab({ id, children }: { id: string; children: ReactNode }) {
const { active, setActive } = useTabsContext()
return (
<button
role="tab"
aria-selected={active === id}
onClick={() => setActive(id)}
>
{children}
</button>
)
}
function TabPanel({ id, children }: { id: string; children: ReactNode }) {
const { active } = useTabsContext()
return active === id ? <div role="tabpanel">{children}</div> : null
}
// Attach as static properties for ergonomic usage
Tabs.List = TabList
Tabs.Tab = Tab
Tabs.Panel = TabPanel
// Usage:
// <Tabs defaultTab="overview">
// <Tabs.List>
// <Tabs.Tab id="overview">Overview</Tabs.Tab>
// <Tabs.Tab id="details">Details</Tabs.Tab>
// </Tabs.List>
// <Tabs.Panel id="overview"><p>Overview content</p></Tabs.Panel>
// <Tabs.Panel id="details"><p>Details content</p></Tabs.Panel>
// </Tabs>19. useId & useTransition
useId — stable accessible IDs
import { useId } from 'react'
// Generates a unique, stable ID that is consistent across server and client
// — critical for avoiding hydration mismatches with SSR
function LabeledInput({ label }: { label: string }) {
const id = useId()
return (
<>
<label htmlFor={id}>{label}</label>
<input id={id} />
</>
)
}
// Multiple IDs from one call using a suffix
function Form() {
const baseId = useId()
return (
<form>
<label htmlFor={`${baseId}-name`}>Name</label>
<input id={`${baseId}-name`} />
<label htmlFor={`${baseId}-email`}>Email</label>
<input id={`${baseId}-email`} />
</form>
)
}useTransition — keep UI responsive during heavy updates
import { useState, useTransition } from 'react'
function SearchPage({ allItems }: { allItems: string[] }) {
const [query, setQuery] = useState('')
const [results, setResults] = useState(allItems)
const [isPending, startTransition] = useTransition()
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const val = e.target.value
setQuery(val) // urgent — updates the input immediately
startTransition(() => {
// non-urgent — React may defer this if the user is still typing
setResults(allItems.filter((item) =>
item.toLowerCase().includes(val.toLowerCase())
))
})
}
return (
<>
<input value={query} onChange={handleChange} placeholder="Search…" />
{isPending && <p>Updating results…</p>}
<ul>
{results.map((item) => <li key={item}>{item}</li>)}
</ul>
</>
)
}20. Hook Rules & Anti-patterns
Anti-pattern | Problem | Fix |
|---|---|---|
Calling hooks inside loops / conditions | Hook call order changes between renders → state mismatch | Always call hooks at the top level of the component |
Missing dependency in useEffect | Effect reads stale values from a previous render | Add all referenced variables to the deps array; use the eslint-plugin-react-hooks rule |
setState during render | Infinite render loop | Move logic to an event handler or useEffect |
Mutating state directly ( | React does not detect the change; component does not re-render | Always produce a new object/array with spread or |
Using array index as key in a reorderable list | React reuses wrong DOM nodes → broken UI / lost focus | Use a stable, unique ID from the data |
Creating objects/arrays inline in render as props to memo children | New reference every render defeats React.memo | Lift the value outside the component or wrap with useMemo |
Deriving state in useEffect ( | Causes an extra render; can create infinite loops | Compute derived values during render with useMemo or inline |