Toolmingo
Guides12 min read

React Cheat Sheet: JSX, Components, Hooks & Patterns

A complete React cheat sheet — JSX syntax, components, props, state, events, lists, forms, hooks, Context, performance, TypeScript, and common patterns with copy-ready examples.

React is a JavaScript library for building user interfaces with reusable components. This cheat sheet covers the full React API — from JSX basics to advanced patterns — with copy-ready code for everyday development.

Quick reference

The 25 patterns that cover 95% of everyday React development.

Pattern What it does
<Tag attr={value}> JSX element with expression
{condition && <El />} Conditional render
{ok ? <A /> : <B />} Ternary render
{items.map(i => <Li key={i.id} />)} List render
function Cmp({ name }) {} Function component with props
const [n, setN] = useState(0) Local state
setN(prev => prev + 1) Functional state update
useEffect(() => {}, [dep]) Side effect on dep change
useEffect(() => {}, []) Run once (mount)
useEffect(() => () => cleanup(), []) Effect with cleanup
const ref = useRef(null) DOM reference
useMemo(() => compute(), [dep]) Memoize value
useCallback(() => fn(), [dep]) Stable function reference
useContext(MyCtx) Read context value
const [s, dispatch] = useReducer(r, init) Complex state
React.memo(Cmp) Skip re-render if props same
<Suspense fallback={<Spinner />}> Lazy loading boundary
const Lazy = lazy(() => import('./C')) Code-split component
createPortal(el, node) Render outside parent DOM
<ErrorBoundary> Catch render errors
forwardRef((props, ref) => …) Pass ref to child
useImperativeHandle(ref, () => ({})) Expose child API
startTransition(() => setState(v)) Non-urgent update
useDeferredValue(value) Defer expensive renders
"use client" (Next.js) Mark as Client Component

JSX syntax

JSX compiles to React.createElement calls. Every JSX file needs React in scope (or the JSX transform handles it automatically in React 17+).

// Basic element
const el = <h1 className="title">Hello</h1>;

// Self-closing
const img = <img src="/logo.png" alt="Logo" />;

// Expressions (any JS value, not statements)
const name = "World";
const greeting = <p>Hello, {name}!</p>;

// Multi-line — wrap in parentheses, one root element
const card = (
  <div className="card">
    <h2>{title}</h2>
    <p>{body}</p>
  </div>
);

// Fragment — avoid extra DOM node
const fragment = (
  <>
    <dt>Term</dt>
    <dd>Definition</dd>
  </>
);

// Attributes: camelCase, className not class, htmlFor not for
<label htmlFor="email" className="label" style={{ color: "red" }}>
  Email
</label>

// Spread props
const inputProps = { type: "text", placeholder: "Name" };
<input {...inputProps} />;

// Children
<Button onClick={() => save()}>Save</Button>

Components

React components are functions that return JSX (or null).

// Function component
function Greeting({ name, age = 0 }) {
  return (
    <p>
      Hello, {name}! You are {age} years old.
    </p>
  );
}

// Arrow component
const Badge = ({ label, color = "blue" }) => (
  <span className={`badge badge-${color}`}>{label}</span>
);

// Component with children
function Card({ title, children }) {
  return (
    <div className="card">
      <h2>{title}</h2>
      <div className="card-body">{children}</div>
    </div>
  );
}

// Usage
<Card title="Welcome">
  <p>Hello from inside the card.</p>
</Card>

Component naming

  • PascalCase → React treats it as a component (<MyButton />)
  • lowercase → HTML element (<button />)

Props

Props flow down from parent to child. They are read-only inside the component.

// Passing props
<UserCard
  name="Ana"
  age={28}
  active={true}
  tags={["admin", "editor"]}
  onLogout={() => handleLogout()}
/>

// Receiving props
function UserCard({ name, age, active, tags, onLogout }) {
  return (
    <div>
      <h2>{name}</h2>
      <p>Age: {age}</p>
      {active && <span>Active</span>}
      <ul>{tags.map(t => <li key={t}>{t}</li>)}</ul>
      <button onClick={onLogout}>Log out</button>
    </div>
  );
}

// Default props (destructuring defaults)
function Button({ label = "Click me", variant = "primary", onClick }) {
  return <button className={`btn-${variant}`} onClick={onClick}>{label}</button>;
}

// Spread remaining props
function Input({ label, ...rest }) {
  return (
    <label>
      {label}
      <input {...rest} />
    </label>
  );
}

State with useState

import { useState } from "react";

// Primitive
const [count, setCount] = useState(0);
setCount(count + 1);            // direct
setCount(prev => prev + 1);     // functional (safe for async/batched)

// Object state — always spread, never mutate
const [user, setUser] = useState({ name: "", email: "" });
setUser(prev => ({ ...prev, email: "new@example.com" }));

// Array state
const [items, setItems] = useState([]);
setItems(prev => [...prev, newItem]);          // add
setItems(prev => prev.filter(i => i.id !== id)); // remove
setItems(prev => prev.map(i => i.id === id ? { ...i, done: true } : i)); // update

// Boolean toggle
const [open, setOpen] = useState(false);
setOpen(prev => !prev);

// Lazy initializer — runs once, avoids expensive re-computation
const [data, setData] = useState(() => JSON.parse(localStorage.getItem("data") ?? "null"));

Conditional rendering

// && short-circuit (use only when value is boolean — 0 renders as "0"!)
{isLoggedIn && <Dashboard />}
{count > 0 && <Badge count={count} />}   // safe
{items.length > 0 && <List items={items} />} // length is a number — can render 0!
{items.length > 0 && <List items={items} />} // fix: explicit boolean
{!!items.length && <List items={items} />}

// Ternary
{isLoading ? <Spinner /> : <Content />}

// if/else outside JSX
function Page({ status }) {
  if (status === "loading") return <Spinner />;
  if (status === "error") return <Error />;
  return <Content />;
}

// Nullish — render nothing
{shouldShow ? <El /> : null}

Lists and keys

// Always provide a stable, unique key
const list = items.map(item => (
  <li key={item.id}>{item.name}</li>
));

// Key from index (only when list is static and never reordered)
const tabs = ["Home", "About", "Contact"].map((tab, i) => (
  <button key={i}>{tab}</button>
));

// Filtered list
const active = items
  .filter(item => item.active)
  .map(item => <Item key={item.id} {...item} />);

// Nested lists
<ul>
  {categories.map(cat => (
    <li key={cat.id}>
      <strong>{cat.name}</strong>
      <ul>
        {cat.items.map(item => (
          <li key={item.id}>{item.label}</li>
        ))}
      </ul>
    </li>
  ))}
</ul>

Key rules: Keys must be unique among siblings. Don't use Math.random(). Avoid index keys when items can be reordered or filtered.


Events

// Click
<button onClick={() => setCount(c => c + 1)}>+</button>

// Passing arguments
<button onClick={() => deleteItem(item.id)}>Delete</button>

// Event object
<input onChange={(e) => setValue(e.target.value)} />

// Form submit — prevent default
<form onSubmit={(e) => { e.preventDefault(); submit(); }}>

// Common events
onMouseEnter / onMouseLeave
onKeyDown / onKeyUp / onKeyPress (deprecated)
onFocus / onBlur
onScroll
onDragStart / onDrop
onInput / onChange / onSubmit

// Event delegation — not needed in React (React handles it internally)

Forms

// Controlled input — React owns the value
function ContactForm() {
  const [form, setForm] = useState({ name: "", email: "", message: "" });

  const handle = (e) =>
    setForm(prev => ({ ...prev, [e.target.name]: e.target.value }));

  const submit = (e) => {
    e.preventDefault();
    console.log(form);
  };

  return (
    <form onSubmit={submit}>
      <input name="name" value={form.name} onChange={handle} />
      <input name="email" type="email" value={form.email} onChange={handle} />
      <textarea name="message" value={form.message} onChange={handle} />
      <button type="submit">Send</button>
    </form>
  );
}

// Checkbox
const [checked, setChecked] = useState(false);
<input type="checkbox" checked={checked} onChange={e => setChecked(e.target.checked)} />

// Select
const [color, setColor] = useState("blue");
<select value={color} onChange={e => setColor(e.target.value)}>
  <option value="red">Red</option>
  <option value="blue">Blue</option>
</select>

// Uncontrolled input (ref)
const inputRef = useRef(null);
<input ref={inputRef} defaultValue="initial" />
// Read: inputRef.current.value

useEffect

import { useEffect } from "react";

// Run on every render
useEffect(() => { document.title = count; });

// Run once (mount)
useEffect(() => {
  fetchData().then(setData);
}, []);

// Run when dep changes
useEffect(() => {
  const sub = subscribe(userId);
  return () => sub.unsubscribe(); // cleanup
}, [userId]);

// Data fetching with cleanup
useEffect(() => {
  let cancelled = false;
  async function load() {
    const res = await fetch(`/api/users/${id}`);
    const data = await res.json();
    if (!cancelled) setUser(data);
  }
  load();
  return () => { cancelled = true; };
}, [id]);

// Abort controller pattern
useEffect(() => {
  const controller = new AbortController();
  fetch(`/api/items`, { signal: controller.signal })
    .then(r => r.json())
    .then(setItems)
    .catch(e => { if (e.name !== "AbortError") setError(e); });
  return () => controller.abort();
}, []);

useRef

import { useRef } from "react";

// DOM reference
const inputRef = useRef(null);
<input ref={inputRef} />;
// inputRef.current.focus();

// Mutable value that doesn't trigger re-render
const timerRef = useRef(null);
timerRef.current = setInterval(() => tick(), 1000);
// clearInterval(timerRef.current);

// Track previous value
function usePrevious(value) {
  const ref = useRef();
  useEffect(() => { ref.current = value; }, [value]);
  return ref.current; // previous render's value
}

Context

Use Context to share values across the tree without prop drilling.

import { createContext, useContext, useState } from "react";

// 1. Create
const ThemeContext = createContext("light");

// 2. Provide
function App() {
  const [theme, setTheme] = useState("light");
  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      <Layout />
    </ThemeContext.Provider>
  );
}

// 3. Consume
function Button() {
  const { theme, setTheme } = useContext(ThemeContext);
  return (
    <button
      className={`btn-${theme}`}
      onClick={() => setTheme(t => (t === "light" ? "dark" : "light"))}
    >
      Toggle theme
    </button>
  );
}

// Custom hook pattern (recommended)
function useTheme() {
  const ctx = useContext(ThemeContext);
  if (!ctx) throw new Error("useTheme must be used inside ThemeContext.Provider");
  return ctx;
}

useReducer

Use for complex state with multiple sub-values or when next state depends on previous.

import { useReducer } from "react";

type Action =
  | { type: "increment" }
  | { type: "decrement" }
  | { type: "reset"; payload: number };

function reducer(state: number, action: Action): number {
  switch (action.type) {
    case "increment": return state + 1;
    case "decrement": return state - 1;
    case "reset":     return action.payload;
    default:          return state;
  }
}

function Counter() {
  const [count, dispatch] = useReducer(reducer, 0);
  return (
    <>
      <p>{count}</p>
      <button onClick={() => dispatch({ type: "increment" })}>+</button>
      <button onClick={() => dispatch({ type: "decrement" })}>−</button>
      <button onClick={() => dispatch({ type: "reset", payload: 0 })}>Reset</button>
    </>
  );
}

Performance

React.memo

// Skip re-render if props haven't changed (shallow comparison)
const ExpensiveList = React.memo(function ExpensiveList({ items }) {
  return <ul>{items.map(i => <li key={i.id}>{i.name}</li>)}</ul>;
});

// Custom comparison
const List = React.memo(MyList, (prev, next) => prev.ids.join() === next.ids.join());

useMemo and useCallback

// Memoize expensive computation
const sorted = useMemo(() => [...items].sort(byDate), [items]);

// Stable function reference for memoized child / useEffect deps
const handleDelete = useCallback((id: string) => {
  setItems(prev => prev.filter(i => i.id !== id));
}, []); // empty — setItems is stable

Code splitting

import { lazy, Suspense } from "react";

const Settings = lazy(() => import("./Settings"));

function App() {
  return (
    <Suspense fallback={<div>Loading…</div>}>
      <Settings />
    </Suspense>
  );
}

Custom hooks

Extract stateful logic into reusable hooks. Name must start with use.

// useLocalStorage
function useLocalStorage<T>(key: string, initial: T) {
  const [value, setValue] = useState<T>(() => {
    try {
      const raw = localStorage.getItem(key);
      return raw !== null ? (JSON.parse(raw) as T) : initial;
    } catch {
      return initial;
    }
  });

  const set = (v: T) => {
    setValue(v);
    localStorage.setItem(key, JSON.stringify(v));
  };

  return [value, set] as const;
}

// useDebounce
function useDebounce<T>(value: T, delay: number): T {
  const [debounced, setDebounced] = useState(value);
  useEffect(() => {
    const id = setTimeout(() => setDebounced(value), delay);
    return () => clearTimeout(id);
  }, [value, delay]);
  return debounced;
}

// useFetch
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(() => {
    setLoading(true);
    fetch(url)
      .then(r => { if (!r.ok) throw new Error(r.statusText); return r.json(); })
      .then(setData)
      .catch(setError)
      .finally(() => setLoading(false));
  }, [url]);

  return { data, loading, error };
}

forwardRef

Pass a ref from a parent to a DOM element inside a child component.

import { forwardRef } from "react";

const FancyInput = forwardRef<HTMLInputElement, { label: string }>(
  ({ label }, ref) => (
    <label>
      {label}
      <input ref={ref} className="fancy" />
    </label>
  )
);

// Parent
const inputRef = useRef<HTMLInputElement>(null);
<FancyInput label="Name" ref={inputRef} />;
// inputRef.current?.focus();

Portals

Render a child outside the parent DOM hierarchy (for modals, tooltips).

import { createPortal } from "react-dom";

function Modal({ children, onClose }) {
  return createPortal(
    <div className="modal-overlay" onClick={onClose}>
      <div className="modal" onClick={e => e.stopPropagation()}>
        {children}
      </div>
    </div>,
    document.getElementById("modal-root")!
  );
}

Error boundaries

Catch JavaScript errors in the component tree. Must be class components.

class ErrorBoundary extends React.Component<
  { fallback: React.ReactNode; children: React.ReactNode },
  { error: Error | null }
> {
  state = { error: null };

  static getDerivedStateFromError(error: Error) {
    return { error };
  }

  componentDidCatch(error: Error, info: React.ErrorInfo) {
    console.error("ErrorBoundary caught:", error, info.componentStack);
  }

  render() {
    if (this.state.error) return this.props.fallback;
    return this.props.children;
  }
}

// Usage
<ErrorBoundary fallback={<p>Something went wrong.</p>}>
  <RiskyComponent />
</ErrorBoundary>

TypeScript patterns

// Component props type
interface ButtonProps {
  label: string;
  variant?: "primary" | "secondary";
  disabled?: boolean;
  onClick: () => void;
}

function Button({ label, variant = "primary", disabled = false, onClick }: ButtonProps) {
  return (
    <button className={`btn-${variant}`} disabled={disabled} onClick={onClick}>
      {label}
    </button>
  );
}

// Children prop
interface WrapperProps {
  children: React.ReactNode;
  className?: string;
}

// Event types
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
onSubmit: (e: React.FormEvent<HTMLFormElement>) => void;
onClick: (e: React.MouseEvent<HTMLButtonElement>) => void;
onKeyDown: (e: React.KeyboardEvent<HTMLInputElement>) => void;

// Typed useState
const [user, setUser] = useState<User | null>(null);

// Typed useRef
const ref = useRef<HTMLDivElement>(null);

// Component with generic
function Select<T extends string>({
  options,
  value,
  onChange,
}: {
  options: T[];
  value: T;
  onChange: (v: T) => void;
}) {
  return (
    <select value={value} onChange={e => onChange(e.target.value as T)}>
      {options.map(o => <option key={o} value={o}>{o}</option>)}
    </select>
  );
}

Common mistakes

Mistake Why it breaks Fix
Mutating state directly React doesn't detect the change Use spread / [...arr]
Missing key on list items Broken reconciliation, console warning Use stable unique key
Stale closure in useEffect Effect captures old state value Add dep to array or use functional updater
0 && <El /> renders 0 0 is falsy but renderable Use count > 0 && <El /> or ternary
Calling hooks conditionally Breaks hook order rule Always call hooks at top level
useEffect with object dep New reference every render → infinite loop Memoize object or use primitive deps
Async function directly in useEffect Can't mark effect as async Define async fn inside, call it immediately
React.memo on every component Adds overhead for cheap renders Only memo expensive/frequently re-rendered

React hooks overview

Hook Purpose
useState Local state
useEffect Side effects (fetch, subscriptions, timers)
useRef DOM reference / mutable value without re-render
useMemo Memoize expensive value
useCallback Stable function reference
useContext Read context value
useReducer Complex state machine
useLayoutEffect Like useEffect but fires before paint (DOM measurements)
useId Stable unique ID for accessibility
useTransition Mark state update as non-urgent
useDeferredValue Defer re-render for expensive child
useImperativeHandle Expose imperative API via ref

FAQ

Q: When should I use useReducer instead of useState?
When state has multiple sub-values that update together, when next state depends on current state in complex ways, or when you want to colocate state logic (the reducer) away from JSX.

Q: Why does my useEffect run twice in development?
React 18 Strict Mode runs effects twice (mount → unmount → mount) to detect side effects that aren't properly cleaned up. This only happens in development. Ensure your effect returns a cleanup function.

Q: What is the difference between useEffect and useLayoutEffect?
useEffect runs asynchronously after the browser paints. useLayoutEffect runs synchronously after DOM mutations but before paint — use it for DOM measurements (element size/position) to avoid flickering.

Q: When should I reach for Context vs a state management library?
Context works well for low-frequency updates (theme, current user, locale). For high-frequency or deeply nested state (shopping cart, filters, real-time data), use a library like Zustand, Jotai, or Redux Toolkit to avoid unnecessary re-renders.

Q: Is React.memo the same as useMemo?
No. React.memo wraps a component and skips re-rendering if props haven't changed. useMemo memoizes a computed value inside a component. useCallback memoizes a function.

Q: How do I share state between sibling components?
Lift state to the closest common ancestor and pass it down as props. For deeply nested sharing, use Context. For app-wide state, use a state management library.

Related tools

Keep reading

All Toolmingotools are free & run in your browser

No sign-up, no upload, no watermark. Your files never leave your device.

Browse all tools