React
Printed from:
Complete React Cheatsheet
Targets React 19 (current). Notes Server Components and the "use client" / "use server" directives where relevant.
Table of Contents
- Setup
- JSX Basics
- Components & Props
- Hooks Reference
- State
- Effects & Refs
- Context
- Performance
- Suspense & Transitions
- Server Components & Actions
- Forms (React 19)
- Error Boundaries
- Portals
- TypeScript
- Testing
- Routing & Frameworks
- Common Patterns
- Quick Reference
Setup
12345678# Recommended: a framework (Next.js, Remix/React Router, TanStack Start, Expo, Vite)
pnpm create next-app
pnpm create vite@latest my-app --template react-ts
# Toolchain-only install (for libraries)
pnpm add react@latest react-dom@latest
pnpm add -D @types/react @types/react-dom
123456// Mount (Vite / CRA-style)
import { createRoot } from "react-dom/client";
import App from "./App";
createRoot(document.getElementById("root")).render(<App />);
JSX Basics
123456789function Hello({ name }) {
const greeting = `Hi, ${name}`;
return (
<div className="card" style={{ padding: 8 }}>
<h1>{greeting}</h1>
{name === "Ada" && <span>(Lovelace)</span>}
{name ? <em>{name}</em> : null}
<ul>{["a", "b", "c"].map(x => <li key={x}>{x}</li>)}</ul>
<button onClick={(e) => console.log(e)}>Click</button>
<label htmlFor="x">x</label>
<input id="x" defaultValue="" />
<>{/* fragments */}</>
{/* {comments inside JSX} */}
</div>
);
}
Special props: className (not class), htmlFor (not for), key, ref, dangerouslySetInnerHTML={{ __html: "..." }}.
Components & Props
12345678910111213141516// Function component (the only kind you should write now)
function Avatar({ user, size = 40, children }) {
return <img src={user.avatar} width={size} alt={user.name}>{children}</img>;
}
// Default + named exports
export default Avatar;
export { Avatar };
// Composition (preferred over inheritance)
function Card({ children, footer }) {
return (
<section>
<div>{children}</div>
{footer && <footer>{footer}</footer>}
</section>
);
}
key and lists
Keys must be stable, unique per sibling. Avoid array index when items can reorder.
Hooks Reference
| Hook | Purpose |
|---|---|
useState | Local state |
useReducer | Reducer-style state |
useEffect | Side effects after render |
useLayoutEffect | Like effect, synchronous before paint |
useInsertionEffect | CSS-in-JS injection (rare) |
useRef | Mutable ref / DOM node |
useImperativeHandle | Customise ref exposed by forwardRef |
useContext | Read context |
useMemo | Memoise a value |
useCallback | Memoise a function |
useTransition | Mark updates as non-urgent |
useDeferredValue | Defer a value |
useId | Stable unique IDs |
useSyncExternalStore | Subscribe to an external store |
useDebugValue | DevTools label |
| React 19 | |
use | Read promises / context anywhere |
useActionState | Form action + pending + state |
useFormStatus | Read parent form's pending state |
useOptimistic | Optimistic updates |
Rules:
- Hooks run only at the top level of components / custom hooks.
- Call them in the same order every render — no
if/forwraps.
State
123456789101112131415import { useState } from "react";
const [count, setCount] = useState(0);
setCount(count + 1); // direct
setCount(c => c + 1); // updater (use for derived updates)
// Lazy initial state (only runs once)
const [items, setItems] = useState(() => expensiveInit());
// Object/array — replace, don't mutate
setUser({ ...user, name: "Ada" });
setItems([...items, newItem]);
setItems(items.filter(i => i.id !== id));
setItems(items.map(i => i.id === id ? { ...i, done: true } : i));
useReducer
1234567891011function reducer(state, action) {
switch (action.type) {
case "inc": return { ...state, n: state.n + 1 };
case "set": return { ...state, n: action.value };
default: return state;
}
}
const [state, dispatch] = useReducer(reducer, { n: 0 });
dispatch({ type: "inc" });
Effects & Refs
12345678910111213141516171819202122import { useEffect, useRef } from "react";
useEffect(() => {
const id = setInterval(tick, 1000);
return () => clearInterval(id); // cleanup
}, [tick]); // deps
useEffect(() => { /* every render */ });
useEffect(() => { /* once after mount */ }, []);
// Refs
const inputRef = useRef(null);
<input ref={inputRef} />
inputRef.current.focus();
// Mutable value that doesn't trigger render
const renders = useRef(0);
renders.current++;
// React 19: ref-as-prop (no more forwardRef needed)
function MyInput({ ref, ...rest }) { return <input ref={ref} {...rest} />; }
When NOT to use useEffect
- Deriving state from props → just compute it during render.
- Resetting state on prop change → use
keyto remount. - Event-driven logic (button click) → put it in the handler, not an effect.
Context
12345678910111213141516import { createContext, useContext } from "react";
const ThemeContext = createContext("light");
// React 19: <Context value=> directly (no .Provider needed)
<ThemeContext value="dark">
<App />
</ThemeContext>
// Older / current: works too
<ThemeContext.Provider value="dark">
<App />
</ThemeContext.Provider>
function Button() {
const theme = useContext(ThemeContext);
// Or React 19: const theme = use(ThemeContext);
return <button className={theme} />;
}
Context re-renders all consumers when value changes — split contexts if needed, or memoise the value object.
Performance
1234567891011import { memo, useMemo, useCallback } from "react";
// Skip re-render when props are shallowly equal
const Row = memo(function Row({ item }) { return <li>{item.name}</li>; });
// Memoise expensive computation
const sorted = useMemo(() => heavySort(items), [items]);
// Stable function identity (only useful for memoised children / deps)
const onClick = useCallback(() => doIt(id), [id]);
React Compiler (RC): an optional Babel plugin/SWC integration auto-memoises components — when enabled, most manual
memo/useMemo/useCallbackbecomes unnecessary.
Suspense & Transitions
1234567891011121314151617import { Suspense, useTransition, useDeferredValue, use } from "react";
<Suspense fallback={<Spinner />}>
<Posts />
</Suspense>
// useTransition — mark a state update as non-urgent
const [isPending, startTransition] = useTransition();
startTransition(() => setQuery(input));
// useDeferredValue — render lower-priority value
const deferredQuery = useDeferredValue(query);
// use() — unwrap a promise inside a Suspense boundary
function Posts({ postsPromise }) {
const posts = use(postsPromise);
return <ul>{posts.map(p => <li key={p.id}>{p.title}</li>)}</ul>;
}
Server Components & Actions
(Used in frameworks like Next.js, Waku, RedwoodJS — not in plain Vite SPAs.)
1234567891011121314151617181920// Server Component — default; runs only on server
async function PostList() {
const posts = await db.posts.findMany();
return <ul>{posts.map(p => <li key={p.id}>{p.title}</li>)}</ul>;
}
// Client Component
"use client";
import { useState } from "react";
export function Counter() {
const [n, setN] = useState(0);
return <button onClick={() => setN(n+1)}>{n}</button>;
}
// Server Action — callable from client or server
"use server";
export async function createPost(formData) {
await db.post.create({ data: { title: formData.get("title") } });
}
Forms (React 19)
1234567891011121314151617181920212223242526272829303132333435// Pass an action directly to <form>
<form action={createPost}>
<input name="title" />
<SubmitButton />
</form>
// Track pending state from inside the form
"use client";
import { useFormStatus } from "react-dom";
function SubmitButton() {
const { pending } = useFormStatus();
return <button disabled={pending}>{pending ? "Saving..." : "Save"}</button>;
}
// useActionState — combine action, state, pending
"use client";
import { useActionState } from "react";
function Login() {
const [state, action, pending] = useActionState(
async (prev, formData) => {
const r = await login(formData);
return r.ok ? { ok: true } : { error: r.error };
},
{}
);
return (
<form action={action}>
<input name="email" />
<button disabled={pending}>Sign in</button>
{state?.error && <p>{state.error}</p>}
</form>
);
}
// useOptimistic — show pending value before server confirms
import { useOptimistic } from "react";
const [optimistic, addOptimistic] = useOptimistic(messages, (s, m) => [...s, m]);
Error Boundaries
123456789101112131415// Class component (still required for error boundaries)
import { Component } from "react";
class ErrorBoundary extends Component {
state = { error: null };
static getDerivedStateFromError(error) { return { error }; }
componentDidCatch(error, info) { /* log */ }
render() {
if (this.state.error) return <p>Something broke.</p>;
return this.props.children;
}
}
// Most apps use `react-error-boundary` (npm) for a hook-friendly API.
Suspense boundaries handle async-loading errors thrown from use() / promises; class boundaries handle render errors.
Portals
123456789import { createPortal } from "react-dom";
function Modal({ children }) {
return createPortal(
<div className="modal">{children}</div>,
document.body
);
}
TypeScript
1234567891011121314151617181920// Props
type Props = { name: string; count?: number; children?: React.ReactNode };
function Hello({ name, count = 1 }: Props) { return <h1>{name} × {count}</h1>; }
// Events
function onChange(e: React.ChangeEvent<HTMLInputElement>) {}
function onClick(e: React.MouseEvent<HTMLButtonElement>) {}
// Hooks
const [n, setN] = useState<number>(0);
const ref = useRef<HTMLInputElement>(null);
// Generic component
function List<T>({ items, render }: { items: T[]; render: (x: T) => React.ReactNode }) {
return <ul>{items.map((x, i) => <li key={i}>{render(x)}</li>)}</ul>;
}
// React 19 ref-as-prop
type InputProps = React.InputHTMLAttributes<HTMLInputElement> & { ref?: React.Ref<HTMLInputElement> };
Testing
12pnpm add -D vitest @testing-library/react @testing-library/jest-dom jsdom
1234567891011// Counter.test.jsx
import { render, screen, fireEvent } from "@testing-library/react";
import { test, expect } from "vitest";
import Counter from "./Counter";
test("increments", () => {
render(<Counter />);
fireEvent.click(screen.getByRole("button"));
expect(screen.getByText("1")).toBeInTheDocument();
});
Tools: Vitest / Jest runner, React Testing Library for queries, Playwright / Cypress for E2E.
Routing & Frameworks
Pick a framework — React itself no longer recommends building SPAs from scratch:
- Next.js — file-system routing, RSC, server actions, deployable anywhere.
- React Router v7 / Remix — nested routing, loaders, actions, SSR.
- TanStack Start — file-based routing with type-safe loaders.
- Expo (React Native) — universal apps.
- Vite + React Router — classic SPA when you really need it.
12345678910// React Router v7 (data router)
import { createBrowserRouter, RouterProvider, Outlet } from "react-router";
const router = createBrowserRouter([
{ path: "/", element: <Layout />, children: [
{ index: true, element: <Home /> },
{ path: "posts/:id", element: <Post />, loader: postLoader },
]}
]);
<RouterProvider router={router} />
Common Patterns
Controlled vs uncontrolled inputs
123456// Controlled
<input value={text} onChange={e => setText(e.target.value)} />
// Uncontrolled (read on submit)
<input defaultValue="" ref={ref} />
Lifting state
12345function Parent() {
const [v, setV] = useState("");
return <><Input value={v} onChange={setV} /><Display value={v} /></>;
}
Compound components
1234function Tabs({ children }) { /* shared context */ }
Tabs.List = TabsList;
Tabs.Panel = TabsPanel;
Render props / hooks
Prefer custom hooks (useToggle, useDebounce, …) to render-props in modern React.
useEffect for fetching → don't
Prefer a framework loader, React Query / SWR, or use() inside a Suspense boundary.
Quick Reference
1234567891011121314151617181920212223242526// Component
function X({a, b=1, children}) { return <div>{a} {b}{children}</div>; }
// Hooks
const [s, setS] = useState(0);
const [state, dispatch] = useReducer(reducer, init);
useEffect(() => { /* */ }, [deps]);
const ref = useRef(null);
const v = useMemo(() => f(), [deps]);
const fn = useCallback(() => g(), [deps]);
const ctx = useContext(MyCtx);
const id = useId();
const [pending, start] = useTransition();
const deferred = useDeferredValue(value);
// React 19
const data = use(promise);
const [state, action, pending] = useActionState(fn, init);
const { pending } = useFormStatus();
const [optimistic, addOpt] = useOptimistic(state, reducer);
// JSX
<el className="" style={{...}} onClick={() => {}} key={id} ref={ref} />
{cond && <X />} {cond ? <A/> : <B/>} {arr.map(x => <li key={x.id} />)}
<>fragments</>
Tip: for any new React project in 2025+, start with a framework (Next.js, React Router v7, TanStack Start). The plain SPA is now a niche choice, not the default.
Continue Learning
Discover more cheatsheets to boost your productivity