React (software)/Cheat sheet

Last edited by disfordave on 27/08/2026, 06:04:37 UTC

React (software) / Cheat sheet

You were redirected here from React (software) cheat sheet.

Contents

Check out the other cheat sheets!

A fast, developer-friendly reference for React — covering components, JSX, state, hooks, effects, forms, async UI, performance, Suspense, Actions, Server Components, routing, testing, and the patterns you'll actually use.

[!TIP] This cheat sheet focuses on modern function-component React. Class components still exist, but most new React code uses functions and Hooks.


⚛️ Core Concepts

What is React?

React is a JavaScript library for building user interfaces from reusable components.

Think:

data / state ↓ components ↓ JSX ↓ React render ↓ DOM

Core ideas:

  • Declarative — describe what the UI should look like.
  • Component-based — split interfaces into reusable pieces.
  • State-driven — UI changes when state changes.
  • One-way data flow — data generally flows down through props.
  • Composition-first — combine components instead of relying on inheritance.
  • Declarative events — attach event handlers through JSX.
  • Efficient reconciliation — React determines what DOM work is required after rendering.

[!NOTE] React components don't directly "update the DOM." They return a description of the UI. React compares renders and commits the necessary changes.


React Mental Model

A component is roughly:

props + state ↓ component ↓ JSX

Example:

function Greeting({ name }) { return <h1>Hello, {name}!</h1>; }

Think of rendering as:

UI = f(props, state);

React expects rendering to be pure:

function Price({ amount }) { return <span>${amount.toFixed(2)}</span>; }

Avoid side effects during render:

function BadComponent() { localStorage.setItem("rendered", "yes"); // ❌ side effect during render return <div>Hello</div>; }

Use event handlers or Effects for side effects instead.


Render vs Commit

React UI updates roughly happen in two major stages:

Trigger ↓ Render ↓ Commit

Render

React calls your components and calculates what the UI should become.

Commit

React applies the necessary changes to the DOM.

After the commit, Effects may run.

state update ↓ render components ↓ calculate UI ↓ commit DOM changes ↓ run layout effects ↓ browser paints ↓ run normal effects

🚀 Basic Setup

Browser Entry Point

import { createRoot } from "react-dom/client"; import App from "./App.jsx"; const root = createRoot(document.getElementById("root")); root.render(<App />);

Typical HTML:

<!doctype html> <html> <head> <meta charset="UTF-8" /> <title>React App</title> </head> <body> <div id="root"></div> <script type="module" src="/src/main.jsx"></script> </body> </html>

With StrictMode

import { StrictMode } from "react"; import { createRoot } from "react-dom/client"; import App from "./App.jsx"; createRoot(document.getElementById("root")).render( <StrictMode> <App /> </StrictMode> );

StrictMode enables additional development checks.

[!IMPORTANT] Development behavior under StrictMode can intentionally cause some code to run more than once to expose unsafe side effects. Do not assume development render counts exactly match production.


🧩 Components

Functional Component

function Welcome() { return <h1>Hello!</h1>; }

Use it like an HTML element:

function App() { return <Welcome />; }

Component with Props

function Welcome({ name }) { return <h1>Hello, {name}!</h1>; }

Usage:

<Welcome name="Ada" /> <Welcome name="Grace" />

Arrow Function Component

const Welcome = ({ name }) => { return <h1>Hello, {name}!</h1>; };

Concise form:

const Welcome = ({ name }) => <h1>Hello, {name}!</h1>;

Component Naming

Components must start with a capital letter:

function UserCard() { return <div>User</div>; }

Not:

function userCard() { return <div>User</div>; }

React interprets lowercase JSX names as DOM elements:

<div /> button input section

and capitalized names as components:

<UserCard /> <Button /> <Dashboard />

Component Composition

function Avatar({ src, alt }) { return <img src={src} alt={alt} />; } function UserCard({ user }) { return ( <article> <Avatar src={user.avatar} alt={user.name} /> <h2>{user.name}</h2> </article> ); }

Composition is one of React's most important patterns.

App ├── Header ├── Sidebar └── Main ├── UserCard ├── UserCard └── UserCard

🧾 JSX

JSX is syntax that looks like HTML but compiles into JavaScript representations of UI.

const element = <h1>Hello!</h1>;

Basic JSX Rules

Use one root element:

return ( <div> <h1>Hello</h1> <p>World</p> </div> );

Or use a Fragment:

return ( <> <h1>Hello</h1> <p>World</p> </> );

JavaScript Expressions in JSX

Use {}:

const name = "Ada"; return <h1>Hello, {name}!</h1>;

Expressions work:

<p>{2 + 2}</p> <p>{user.name}</p> <p>{items.length}</p> <p>{isAdmin ? "Admin" : "User"}</p>

Statements do not go directly inside JSX:

// ❌ <div> {if (ready) {}} </div>

Calculate before rendering instead:

let message; if (ready) { message = "Ready!"; } return <div>{message}</div>;

JSX Attributes

HTML:

<div class="box"></div>

React:

<div className="box"></div>

Inline expressions:

<img src={user.avatar} alt={user.name} />

Boolean props:

<button disabled={isSaving}>Save</button>

Equivalent shorthand:

<Input required />

means:

<Input required={true} />

Common JSX Attribute Differences

HTML JSX ----------------------------- class className for htmlFor onclick onClick tabindex tabIndex maxlength maxLength readonly readOnly

Example:

<label htmlFor="email">Email</label> <input id="email" maxLength={100} />

JSX Comments

return ( <div> {/* This is a JSX comment */} <p>Hello!</p> </div> );

JSX Automatically Escapes Text

const userInput = "<script>alert('x')</script>"; return <p>{userInput}</p>;

React renders it as text rather than executing it as HTML.

[!WARNING] Be very careful with dangerouslySetInnerHTML. It can create XSS vulnerabilities when used with untrusted HTML.


🧱 Fragments

Avoid unnecessary DOM wrappers:

function Profile() { return ( <> <h1>Ada</h1> <p>Developer</p> </> ); }

Explicit fragment syntax:

import { Fragment } from "react"; function List() { return ( <Fragment> <li>One</li> <li>Two</li> </Fragment> ); }

Use explicit Fragment when a key is needed:

items.map(item => ( <Fragment key={item.id}> <dt>{item.term}</dt> <dd>{item.definition}</dd> </Fragment> ));

🧠 Props

Props are inputs passed from a parent component to a child.

function Greeting({ name }) { return <p>Hi, {name}!</p>; } function App() { return <Greeting name="Ada" />; }

Multiple Props

function UserCard({ name, age, online }) { return ( <div> <h2>{name}</h2> <p>{age}</p> <p>{online ? "Online" : "Offline"}</p> </div> ); }

Usage:

<UserCard name="Ada" age={36} online={true} />

Default Values

Prefer parameter defaults:

function Greeting({ name = "Stranger" }) { return <p>Hello, {name}!</p>; }

Rest Props

function Button({ children, variant = "primary", ...props }) { return ( <button className={`button ${variant}`} {...props}> {children} </button> ); }

Usage:

<Button type="submit" disabled={loading} aria-label="Save" > Save </Button>

Passing Objects

const user = { name: "Ada", age: 36 }; <UserCard user={user} />
function UserCard({ user }) { return <p>{user.name}</p>; }

Passing Functions

function Button({ onPress }) { return <button onClick={onPress}>Click</button>; }
<Button onPress={() => console.log("clicked")} />

This is a common way for children to communicate events upward.


🧒 children

Nested JSX becomes the children prop.

function Card({ children }) { return <div className="card">{children}</div>; }

Usage:

<Card> <h2>Title</h2> <p>Content</p> </Card>

Wrapper Pattern

function Panel({ title, children }) { return ( <section className="panel"> <h2>{title}</h2> {children} </section> ); }
<Panel title="Settings"> <AccountSettings /> </Panel>

⚙️ State

State is data that belongs to a component and can change over time.

import { useState } from "react"; function Counter() { const [count, setCount] = useState(0); return ( <button onClick={() => setCount(count + 1)}> Count: {count} </button> ); }

useState

Syntax:

const [state, setState] = useState(initialValue);

Examples:

const [count, setCount] = useState(0); const [name, setName] = useState(""); const [open, setOpen] = useState(false); const [items, setItems] = useState([]); const [user, setUser] = useState(null);

Changing State

setCount(5);

From current render:

setCount(count + 1);

Using a functional updater:

setCount(prev => prev + 1);

Use the updater form when the next value depends on the previous value.


Multiple Updates

This:

setCount(count + 1); setCount(count + 1); setCount(count + 1);

does not mean "add 3" using the same render's count.

Use updater functions:

setCount(c => c + 1); setCount(c => c + 1); setCount(c => c + 1);

📸 State Is a Snapshot

Each render sees a snapshot of state.

function Counter() { const [count, setCount] = useState(0); function handleClick() { setCount(count + 1); console.log(count); } return <button onClick={handleClick}>{count}</button>; }

Inside that event handler, count still represents the value from the render that created the handler.

Mental model:

render #1 → count = 0 click setCount(1) render #2 → count = 1

State setters schedule another render.

They do not mutate the current render's state variable.


📦 State Batching

React can batch multiple state updates together.

function handleClick() { setName("Ada"); setAge(36); setOpen(true); }

React can process them together before rendering the resulting UI.


🧮 Lazy State Initialization

If initial state is expensive to calculate:

const [data, setData] = useState(() => { return expensiveCalculation(); });

Instead of:

const [data, setData] = useState(expensiveCalculation());

The function form avoids recalculating the initializer unnecessarily during renders.


🧊 Immutable State

Never directly mutate React state.

Bad:

user.name = "Grace"; setUser(user);

Good:

setUser({ ...user, name: "Grace" });

For nested objects:

setUser({ ...user, address: { ...user.address, city: "London" } });

📦 Array State

Add

setItems([ ...items, newItem ]);

Or:

setItems(items => [ ...items, newItem ]);

Remove

setItems( items.filter(item => item.id !== id) );

Update

setItems( items.map(item => item.id === id ? { ...item, done: true } : item ) );

Toggle Property

setTasks( tasks.map(task => task.id === id ? { ...task, done: !task.done } : task ) );

Insert

setItems([ ...items.slice(0, index), newItem, ...items.slice(index) ]);

Sort Without Mutating

Don't:

setItems(items.sort(compareFn));

Use a copied or immutable result:

setItems( [...items].sort(compareFn) );

Or where supported:

setItems( items.toSorted(compareFn) );

Reverse Without Mutating

setItems( items.toReversed() );

Or:

setItems( [...items].reverse() );

Replace One Index

setItems( items.with(index, newValue) );

Or:

setItems( items.map((item, i) => i === index ? newValue : item ) );

Golden Rule

[!TIP] For objects and arrays stored in state, create a new object/array rather than mutating the existing one.

Common mutating methods to watch:

push() pop() shift() unshift() splice() sort() reverse()

Common non-mutating approaches:

map() filter() slice() concat() spread [...] toSorted() toReversed() toSpliced() with()

🧠 Choosing State Structure

Avoid duplicated state:

const [firstName, setFirstName] = useState("Ada"); const [lastName, setLastName] = useState("Lovelace"); // Usually unnecessary: const [fullName, setFullName] = useState("Ada Lovelace");

Derive it:

const fullName = `${firstName} ${lastName}`;

Don't Store What You Can Calculate

Avoid:

const [items, setItems] = useState([]); const [itemCount, setItemCount] = useState(0);

Prefer:

const [items, setItems] = useState([]); const itemCount = items.length;

Sometimes:

const [x, setX] = useState(0); const [y, setY] = useState(0);

can become:

const [position, setPosition] = useState({ x: 0, y: 0 });

But don't group unrelated values just because you can.


⬆️ Lifting State Up

When multiple components need synchronized data, move state to their closest common parent.

function App() { const [selectedId, setSelectedId] = useState(null); return ( <> <List selectedId={selectedId} onSelect={setSelectedId} /> <Details id={selectedId} /> </> ); }

Mental model:

Parent owns state ↓ props Children receive state ↑ callbacks Children request changes

🗝️ Resetting State with key

React preserves component state based on its position in the tree.

You can force a fresh component instance with a different key:

<Profile key={userId} userId={userId} />

When userId changes, React treats it as a different component instance and resets its local state.


🧩 Event Handling

React event names use camelCase:

<button onClick={handleClick}> Click </button>

Not:

<button onclick="handleClick()">

Event Handler

function handleClick() { console.log("Clicked"); } return ( <button onClick={handleClick}> Click </button> );

Pass the function:

onClick={handleClick}

Don't call it immediately:

onClick={handleClick()} // ❌ usually wrong

Inline Handler

<button onClick={() => console.log("clicked")}> Click </button>

Passing Arguments

<button onClick={() => deleteUser(user.id)}> Delete </button>

Event Object

function handleClick(event) { console.log(event.target); console.log(event.currentTarget); }

Common Events

onClick onDoubleClick onChange onInput onSubmit onFocus onBlur onKeyDown onKeyUp onMouseEnter onMouseLeave onPointerDown onPointerMove onPointerUp onScroll

Prevent Default

function handleSubmit(event) { event.preventDefault(); console.log("Submitted"); }

Stop Propagation

function handleClick(event) { event.stopPropagation(); }

📦 Conditional Rendering

if

function Greeting({ loggedIn }) { if (!loggedIn) { return <Login />; } return <Dashboard />; }

Ternary

{isLoggedIn ? <Dashboard /> : <Login />}

Logical AND

{loading && <Spinner />}

Return null

A component may render nothing:

function Warning({ show }) { if (!show) { return null; } return <p>Warning!</p>; }

Watch Out for 0 && ...

{items.length && <List items={items} />}

If length is 0, React may render 0.

Prefer:

{items.length > 0 && <List items={items} />}

🔄 Lists & Keys

Render arrays with .map():

const todos = [ { id: 1, text: "Eat" }, { id: 2, text: "Code" }, { id: 3, text: "Sleep" } ]; return ( <ul> {todos.map(todo => ( <li key={todo.id}> {todo.text} </li> ))} </ul> );

Good Keys

Use stable identifiers:

key={todo.id}

Good:

database ID UUID stable slug persistent unique key

Avoid Array Indexes When Order Changes

todos.map((todo, index) => ( <Todo key={index} todo={todo} /> ));

Index keys can cause confusing state bugs when items are:

inserted removed reordered filtered sorted

An index can be acceptable for truly static lists that never reorder or change identity.


Keys Are Not Passed as Props

<Item key={item.id} id={item.id} />

Inside Item:

function Item({ id }) { // use id }

You cannot read:

props.key

🧱 Forms

Controlled Input

function Form() { const [text, setText] = useState(""); return ( <input value={text} onChange={event => setText(event.target.value)} /> ); }

React state is the source of truth.


Controlled Textarea

<textarea value={message} onChange={event => setMessage(event.target.value)} />

Controlled Checkbox

<input type="checkbox" checked={accepted} onChange={event => setAccepted(event.target.checked)} />

Controlled Select

<select value={country} onChange={event => setCountry(event.target.value)} > <option value="kr">Korea</option> <option value="jp">Japan</option> <option value="us">United States</option> </select>

Form Submission

function LoginForm() { const [email, setEmail] = useState(""); function handleSubmit(event) { event.preventDefault(); console.log(email); } return ( <form onSubmit={handleSubmit}> <input type="email" value={email} onChange={event => setEmail(event.target.value)} /> <button type="submit"> Sign in </button> </form> ); }

Multiple Fields

function Form() { const [form, setForm] = useState({ name: "", email: "" }); function handleChange(event) { const { name, value } = event.target; setForm(form => ({ ...form, [name]: value })); } return ( <> <input name="name" value={form.name} onChange={handleChange} /> <input name="email" value={form.email} onChange={handleChange} /> </> ); }

Uncontrolled Inputs

Sometimes the DOM can own the input value:

function Form() { const inputRef = useRef(null); function handleSubmit(event) { event.preventDefault(); console.log(inputRef.current.value); } return ( <form onSubmit={handleSubmit}> <input ref={inputRef} /> <button>Submit</button> </form> ); }

Use controlled inputs when React needs immediate access to the value.

Use uncontrolled inputs when DOM-managed state is sufficient.


🎯 useRef

A ref stores a mutable value that survives renders without triggering another render when changed.

const ref = useRef(initialValue);

Read/write:

ref.current;

DOM Ref

import { useRef } from "react"; function Search() { const inputRef = useRef(null); function focusInput() { inputRef.current?.focus(); } return ( <> <input ref={inputRef} /> <button onClick={focusInput}> Focus </button> </> ); }

Store Mutable Values

const intervalRef = useRef(null); function start() { intervalRef.current = setInterval(() => { console.log("tick"); }, 1000); } function stop() { clearInterval(intervalRef.current); }

State vs Ref

FeatureStateRef
Persists between renders✅✅
Updating triggers render✅❌
Used to display UI✅Usually ❌
Good for DOM nodes❌✅
Good for timer IDsSometimes✅

Don't Mutate Refs During Render

Avoid:

function Component() { ref.current = Math.random(); // ❌ render side effect return <div />; }

Refs are most useful in handlers and Effects.


🔁 Effects

useEffect synchronizes a component with external systems.

Examples:

network connections subscriptions timers browser APIs third-party widgets DOM APIs outside React analytics

Syntax:

useEffect(() => { // setup return () => { // cleanup }; }, [dependencies]);

Basic Effect

import { useEffect } from "react"; function Component() { useEffect(() => { console.log("Effect ran"); }); return <div>Hello</div>; }

Without a dependency array, it runs after every relevant commit.


Empty Dependency Array

useEffect(() => { console.log("Connect"); return () => { console.log("Disconnect"); }; }, []);

Conceptually:

mount → setup unmount → cleanup

[!IMPORTANT] Development StrictMode may perform an extra setup/cleanup cycle to help detect broken Effects. Write Effects so setup and cleanup are symmetrical.


Dependency Array

useEffect(() => { document.title = `User: ${user.name}`; }, [user.name]);

The Effect re-synchronizes when a dependency changes.


Cleanup

useEffect(() => { const id = setInterval(() => { console.log("tick"); }, 1000); return () => { clearInterval(id); }; }, []);

Event Subscription

useEffect(() => { function handleResize() { console.log(window.innerWidth); } window.addEventListener("resize", handleResize); return () => { window.removeEventListener("resize", handleResize); }; }, []);

Connection Example

useEffect(() => { const connection = createConnection(roomId); connection.connect(); return () => { connection.disconnect(); }; }, [roomId]);

🚫 You Might Not Need an Effect

Do not use Effects for calculations that can happen during rendering.

Bad:

const [fullName, setFullName] = useState(""); useEffect(() => { setFullName(`${firstName} ${lastName}`); }, [firstName, lastName]);

Better:

const fullName = `${firstName} ${lastName}`;

Don't Use Effects for Event Logic

Bad:

useEffect(() => { if (submitted) { sendForm(); } }, [submitted]);

Better:

function handleSubmit() { sendForm(); }

Rule of thumb:

Caused by rendering? → Effect may be appropriate. Caused by user action? → Event handler is usually better.

🌐 Fetching Data in an Effect

Basic pattern:

function User({ userId }) { const [user, setUser] = useState(null); const [error, setError] = useState(null); const [loading, setLoading] = useState(true); useEffect(() => { let ignore = false; async function loadUser() { try { setLoading(true); const response = await fetch(`/api/users/${userId}`); if (!response.ok) { throw new Error(`HTTP ${response.status}`); } const data = await response.json(); if (!ignore) { setUser(data); setError(null); } } catch (error) { if (!ignore) { setError(error); } } finally { if (!ignore) { setLoading(false); } } } loadUser(); return () => { ignore = true; }; }, [userId]); // ... }

Abort Fetch Requests

useEffect(() => { const controller = new AbortController(); async function load() { const response = await fetch("/api/data", { signal: controller.signal }); const data = await response.json(); setData(data); } load().catch(error => { if (error.name !== "AbortError") { console.error(error); } }); return () => { controller.abort(); }; }, []);

[!TIP] For larger apps, frameworks and dedicated data libraries often provide caching, request deduplication, preloading, race-condition handling, mutations, and revalidation more conveniently than handwritten fetch Effects.


📐 useLayoutEffect

Runs before the browser paints after React has committed DOM changes.

useLayoutEffect(() => { const rect = elementRef.current.getBoundingClientRect(); setHeight(rect.height); }, []);

Use primarily for:

DOM measurement layout synchronization visual positioning preventing visible layout flicker

Prefer useEffect unless you specifically need pre-paint layout work.


🧪 useEffectEvent

Useful for Effect logic that needs access to the latest props/state without causing the Effect itself to re-synchronize for those values.

Concept:

const onConnected = useEffectEvent(() => { showNotification("Connected!", theme); }); useEffect(() => { const connection = createConnection(serverUrl, roomId); connection.on("connected", () => { onConnected(); }); connection.connect(); return () => connection.disconnect(); }, [serverUrl, roomId]);

This helps distinguish:

reactive Effect dependencies vs. non-reactive event-like Effect logic

Use it for Effect-related event logic—not as a shortcut for hiding legitimate dependencies.


🧭 useContext

Context lets descendants read a shared value without manually passing it through every intermediate component.

import { createContext, useContext } from "react"; const ThemeContext = createContext("light");

Provider:

function App() { return ( <ThemeContext.Provider value="dark"> <Toolbar /> </ThemeContext.Provider> ); }

Consumer:

function Toolbar() { const theme = useContext(ThemeContext); return <div>Theme: {theme}</div>; }

Common Context Uses

theme authentication locale feature flags shared application state configuration

Props vs Context

Use props when:

data belongs to nearby components the relationship should remain explicit only a few levels are involved

Use context when:

many distant descendants need the same value passing props through intermediates adds noise the value is conceptually global to a subtree

Avoid Putting Everything in Context

A single frequently-changing context:

<AppContext.Provider value={{ user, theme, notifications, cart, mousePosition, timestamp }}>

can cause broad re-rendering.

Often better:

AuthContext ThemeContext CartContext

Split contexts by responsibility.


⚙️ useReducer

Useful for state with multiple related transitions.

import { useReducer } from "react"; function reducer(state, action) { switch (action.type) { case "increment": return { ...state, count: state.count + 1 }; case "decrement": return { ...state, count: state.count - 1 }; default: throw new Error( `Unknown action: ${action.type}` ); } } function Counter() { const [state, dispatch] = useReducer( reducer, { count: 0 } ); return ( <> <button onClick={() => dispatch({ type: "decrement" })} > - </button> <span>{state.count}</span> <button onClick={() => dispatch({ type: "increment" })} > + </button> </> ); }

Reducer Mental Model

current state + action ↓ reducer ↓ next state

Reducers should be pure:

function reducer(state, action) { return nextState; }

Avoid:

fetch() localStorage writes DOM changes random side effects

inside a reducer.


useState vs useReducer

Use useState when:

state is simple updates are straightforward few state transitions exist

Use useReducer when:

many actions affect the same state state transitions are complex you want update logic centralized

🕹️ Custom Hooks

Custom Hooks package reusable stateful logic.

Hook names must start with use.

function useToggle(initial = false) { const [on, setOn] = useState(initial); function toggle() { setOn(value => !value); } return [on, toggle]; }

Usage:

function Menu() { const [open, toggle] = useToggle(); return ( <> <button onClick={toggle}> Toggle </button> {open && <nav>Menu</nav>} </> ); }

Custom Hook with Effect

function useOnlineStatus() { const [online, setOnline] = useState(navigator.onLine); useEffect(() => { function handleOnline() { setOnline(true); } function handleOffline() { setOnline(false); } window.addEventListener( "online", handleOnline ); window.addEventListener( "offline", handleOffline ); return () => { window.removeEventListener( "online", handleOnline ); window.removeEventListener( "offline", handleOffline ); }; }, []); return online; }

Custom Hooks Share Logic, Not State

const statusA = useOnlineStatus(); const statusB = useOnlineStatus();

These Hook calls use the same logic, but each component still has its own Hook state unless that state is stored externally/shared.


📏 Rules of Hooks

Hooks must generally be called:

  1. At the top level of a component.
  2. At the top level of a custom Hook.

Good:

function Component() { const [count, setCount] = useState(0); // ... }

Bad:

if (loggedIn) { const [user, setUser] = useState(null); // ❌ }

Bad:

for (const item of items) { useEffect(() => {}); // ❌ }

Bad:

function handleClick() { useState(0); // ❌ }

[!NOTE] The special use(...) API has different placement rules from normal Hooks and can be used conditionally, but ordinary Hooks like useState, useEffect, and useMemo follow the normal Rules of Hooks.


🆔 useId

Create stable IDs for accessibility relationships.

import { useId } from "react"; function PasswordField() { const hintId = useId(); return ( <> <label> Password <input type="password" aria-describedby={hintId} /> </label> <p id={hintId}> Must contain 8 characters. </p> </> ); }

Do not use useId() to generate list keys.

Use IDs from your data for keys.


🧠 useMemo

Caches the result of a calculation between renders when dependencies have not changed.

const visibleTodos = useMemo( () => filterTodos(todos, tab), [todos, tab] );

Use for calculations that are actually expensive or when stable identity is required for an optimization.


Don't Memoize Everything

Usually unnecessary:

const fullName = useMemo( () => `${firstName} ${lastName}`, [firstName, lastName] );

Simpler:

const fullName = `${firstName} ${lastName}`;

Memoization has its own complexity and overhead.


🔗 useCallback

Caches a function reference between renders.

const handleSave = useCallback(() => { saveDocument(documentId); }, [documentId]);

Conceptually:

useCallback(fn, deps);

is similar to:

useMemo(() => fn, deps);

Common use case:

const handleSubmit = useCallback( data => { save(data); }, [save] ); return ( <MemoizedForm onSubmit={handleSubmit} /> );

🧊 memo

Skip re-rendering a component when its props are unchanged according to React's memoization rules.

import { memo } from "react"; const UserCard = memo(function UserCard({ user }) { return <div>{user.name}</div>; });

Use only where re-rendering is meaningfully expensive.

[!TIP] A re-render is not automatically a performance problem. Measure before optimizing.


⚡ Performance Mental Model

Before reaching for memoization:

1. Keep components pure. 2. Keep state local when possible. 3. Avoid unnecessary Effects. 4. Avoid Effect → state → Effect chains. 5. Measure performance. 6. Optimize identified bottlenecks.

Useful tools:

React DevTools React Profiler browser Performance tools

🚦 useTransition

Mark non-urgent state updates as a Transition.

import { useState, useTransition } from "react"; function Search() { const [query, setQuery] = useState(""); const [resultsQuery, setResultsQuery] = useState(""); const [isPending, startTransition] = useTransition(); function handleChange(event) { const value = event.target.value; setQuery(value); startTransition(() => { setResultsQuery(value); }); } return ( <> <input value={query} onChange={handleChange} /> {isPending && <Spinner />} <Results query={resultsQuery} /> </> ); }

Urgent:

typing click feedback input updates

Potentially non-urgent:

large result lists navigation content expensive UI updates

startTransition

Standalone API:

import { startTransition } from "react"; startTransition(() => { setTab(nextTab); });

Use useTransition when you also need the isPending state.


🐢 useDeferredValue

Defer part of the UI while keeping urgent UI responsive.

const [query, setQuery] = useState(""); const deferredQuery = useDeferredValue(query); return ( <> <SearchInput value={query} onChange={setQuery} /> <SearchResults query={deferredQuery} /> </> );

Mental model:

input value ↓ immediate input UI input value ↓ deferred expensive result UI

⏳ Suspense

Suspense displays a fallback while supported children are waiting.

import { Suspense } from "react"; function App() { return ( <Suspense fallback={<Spinner />}> <Dashboard /> </Suspense> ); }

Common Suspense integrations include:

lazy-loaded component code Suspense-aware data sources Server Components the `use()` API streaming server rendering

Nested Suspense

<Suspense fallback={<PageSkeleton />}> <Profile /> <Suspense fallback={<PostsSkeleton />}> <Posts /> </Suspense> </Suspense>

This lets different parts of a page reveal independently.


📦 lazy

Lazy-load a component:

import { lazy, Suspense } from "react"; const Settings = lazy(() => import("./Settings.jsx")); function App() { return ( <Suspense fallback={<Spinner />}> <Settings /> </Suspense> ); }

Typically the module should default-export the component:

export default function Settings() { return <div>Settings</div>; }

Route-Level Code Splitting

Conceptually:

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

Then render each under appropriate Suspense boundaries.

Frameworks and routers may provide their own route-level lazy loading mechanisms.


📖 use

The use API reads supported resources such as Promises or contexts.

Promise example:

import { Suspense, use } from "react"; function User({ userPromise }) { const user = use(userPromise); return <h2>{user.name}</h2>; } function Page({ userPromise }) { return ( <Suspense fallback={<p>Loading...</p>}> <User userPromise={userPromise} /> </Suspense> ); }

When the Promise is pending, the component suspends and React uses the nearest Suspense fallback.


use with Context

const theme = use(ThemeContext);

Unlike normal Hooks, use() can be called conditionally:

if (showTheme) { const theme = use(ThemeContext); }

Do not create uncached Promises directly during every Client Component render:

function User() { const data = use(fetch("/api/user")); // 🚩 new Promise every render // ... }

Use your framework's Suspense-aware data layer or a stable/cached resource.


🧯 Error Boundaries

Error Boundaries catch rendering errors in descendant components and display fallback UI.

Conceptually:

<ErrorBoundary fallback={<ErrorPage />}> <Dashboard /> </ErrorBoundary>

They are useful around:

routes large page sections third-party widgets async Suspense trees fragile features

Error Boundaries do not generally replace ordinary try/catch for event-handler logic.

async function handleSave() { try { await save(); } catch (error) { setError(error); } }

🌀 Portals

Render children into another DOM location while keeping them part of the same React tree.

import { createPortal } from "react-dom"; function Modal({ children }) { return createPortal( <div className="modal"> {children} </div>, document.body ); }

Useful for:

modals tooltips dialogs dropdown overlays floating UI

React event propagation still follows the React component tree.


🧰 useImperativeHandle

Customize what a parent receives through a ref.

import { useImperativeHandle, useRef } from "react"; function MyInput({ ref }) { const inputRef = useRef(null); useImperativeHandle(ref, () => ({ focus() { inputRef.current?.focus(); } })); return <input ref={inputRef} />; }

Parent:

function Form() { const ref = useRef(null); return ( <> <MyInput ref={ref} /> <button onClick={() => ref.current?.focus()} > Focus </button> </> ); }

Prefer declarative props when possible.

Imperative APIs are useful when integrating with:

focus selection scrolling media playback third-party widgets

⚙️ Actions

Modern React includes APIs for expressing asynchronous user actions and their pending/error/result states.

Conceptual Action:

async function updateName(name) { await saveName(name); }

Actions work especially well with:

forms Transitions optimistic UI Server Functions pending states

📨 Form Actions

A form can receive a function through its action prop:

function ContactForm() { async function submitAction(formData) { const email = formData.get("email"); await saveEmail(email); } return ( <form action={submitAction}> <input name="email" type="email" /> <button type="submit"> Subscribe </button> </form> ); }

FormData values:

formData.get("email"); formData.get("name"); formData.getAll("tags");

🔄 useActionState

Manage state produced by an Action.

import { useActionState } from "react"; async function saveAction( previousState, formData ) { const email = formData.get("email"); if (!email) { return { error: "Email is required" }; } await saveEmail(email); return { success: true }; } function Form() { const [ state, formAction, isPending ] = useActionState( saveAction, null ); return ( <form action={formAction}> <input name="email" /> <button disabled={isPending}> {isPending ? "Saving..." : "Save"} </button> {state?.error && ( <p>{state.error}</p> )} </form> ); }

Returns:

[state, actionDispatcher, isPending]

useReducer vs useActionState

useReducer:

UI state transitions pure reducer no side effects in reducer

useActionState:

Action/result state may be asynchronous can perform side effects often used with forms

⚡ useOptimistic

Display an immediate optimistic result while an Action is pending.

import { startTransition, useOptimistic } from "react"; function LikeButton({ liked, onChange }) { const [ optimisticLiked, setOptimisticLiked ] = useOptimistic(liked); function handleClick() { startTransition(async () => { setOptimisticLiked( !optimisticLiked ); await onChange( !optimisticLiked ); }); } return ( <button onClick={handleClick}> {optimisticLiked ? "♥ Liked" : "♡ Like"} </button> ); }

Mental model:

user action ↓ optimistic UI immediately ↓ server/action runs ↓ real state catches up

Useful for:

likes comments cart quantities messages toggles small edits

📨 useFormStatus

Read the status of a parent form submission.

import { useFormStatus } from "react-dom"; function SubmitButton() { const { pending } = useFormStatus(); return ( <button type="submit" disabled={pending} > {pending ? "Submitting..." : "Submit"} </button> ); }

Usage:

<form action={saveAction}> <input name="title" /> <SubmitButton /> </form>

The component using useFormStatus() needs to be inside the relevant form.


🏃 <Activity>

Activity can keep a subtree around while controlling whether it is visible/active.

Conceptually:

import { Activity } from "react"; <Activity mode={showPanel ? "visible" : "hidden"}> <Panel /> </Activity>

This is useful when you want to preserve parts of the UI rather than fully removing them through conditional rendering.

Compare:

{showPanel && <Panel />}

with an Activity-style pattern where React can retain the subtree.

Use cases can include:

tabs navigation background UI preserving hidden component state preparing likely-next interfaces

💾 Derived vs Stored State

Ask:

Can this value be calculated from current props/state during render?

If yes, usually calculate it.

Bad:

const [total, setTotal] = useState(0); useEffect(() => { setTotal( items.reduce( (sum, item) => sum + item.price, 0 ) ); }, [items]);

Better:

const total = items.reduce( (sum, item) => sum + item.price, 0 );

🧩 Controlled vs Uncontrolled Components

A controlled component receives important state through props.

function Accordion({ open, onOpenChange }) { return ( <button onClick={() => onOpenChange(!open) } > {open ? "Close" : "Open"} </button> ); }

Parent owns the state:

const [open, setOpen] = useState(false); <Accordion open={open} onOpenChange={setOpen} />

Uncontrolled:

function Accordion() { const [open, setOpen] = useState(false); // ... }

Controlled components are easier to coordinate externally.

Uncontrolled components are often simpler for local behavior.


🏗️ Component Design Patterns

Container + Presentational

Data/logic component:

function UserPage() { const user = useUser(); return ( <UserProfile user={user} /> ); }

Presentational:

function UserProfile({ user }) { return ( <article> <h1>{user.name}</h1> </article> ); }

Compound Components

Usage:

<Tabs> <Tabs.List> <Tabs.Tab id="profile"> Profile </Tabs.Tab> <Tabs.Tab id="settings"> Settings </Tabs.Tab> </Tabs.List> <Tabs.Panel id="profile"> ... </Tabs.Panel> </Tabs>

Often implemented using:

Context composition shared parent state

Useful for reusable UI libraries.


Render Props

<DataProvider> {data => ( <Dashboard data={data} /> )} </DataProvider>

Less dominant in modern React because custom Hooks cover many similar use cases, but still useful in some APIs.


Slot Pattern

function Card({ header, children, footer }) { return ( <article> <header>{header}</header> <main>{children}</main> <footer>{footer}</footer> </article> ); }

Usage:

<Card header={<h2>Profile</h2>} footer={<Button>Save</Button>} > <ProfileForm /> </Card>

🌐 Data Fetching Patterns

Common approaches include:

framework-level loaders Server Components Suspense-aware data APIs query/cache libraries route loaders Effects

The right approach depends on your architecture.


Basic Fetch Helper

async function getJSON(url) { const response = await fetch(url); if (!response.ok) { throw new Error( `HTTP ${response.status}` ); } return response.json(); }

Loading/Error/Data State

if (loading) { return <Spinner />; } if (error) { return ( <ErrorMessage error={error} /> ); } return <User user={data} />;

Avoid Waterfalls

Potential waterfall:

App fetches user ↓ waits Profile renders ↓ fetches posts ↓ waits Comments renders ↓ fetches comments

Better architectures can start independent requests earlier or in parallel:

const [ user, posts ] = await Promise.all([ getUser(), getPosts() ]);

🌍 Server Components

React Server Components run in a server environment and can render without sending their component JavaScript to the browser.

Conceptual Server Component:

export default async function Page() { const users = await db.users.findMany(); return ( <ul> {users.map(user => ( <li key={user.id}> {user.name} </li> ))} </ul> ); }

Potential benefits:

access server-side data directly keep server-only dependencies off client reduce client JavaScript stream UI compose server and client components

Server Component support is typically provided by a framework/toolchain.


🖥️ Client Components

In React Server Component environments, interactive browser-side components are marked using:

"use client"; import { useState } from "react"; export default function Counter() { const [count, setCount] = useState(0); return ( <button onClick={() => setCount(c => c + 1) } > {count} </button> ); }

Client Components can use browser-only features such as:

useState useEffect DOM events localStorage window document interactive Hooks

🖧 Server Functions

A server function may use:

"use server";

Example:

export async function saveProfile( formData ) { "use server"; const name = formData.get("name"); // validate // authorize // write to database }

[!WARNING] Treat Server Function arguments as untrusted input. Validate input and perform authorization on the server.


🔐 Server/Client Boundary Mental Model

Server ├── database ├── filesystem ├── secrets ├── Server Components └── Server Functions ↓ serialized boundary Client ├── browser events ├── state ├── effects ├── DOM └── Client Components

Do not expose secrets through props sent to Client Components.


🧭 Routing

React itself does not require a particular router.

A common choice is React Router.

Modern declarative usage looks conceptually like:

import { BrowserRouter, Link, Route, Routes } from "react-router"; function App() { return ( <BrowserRouter> <nav> <Link to="/"> Home </Link> <Link to="/about"> About </Link> </nav> <Routes> <Route path="/" element={<Home />} /> <Route path="/about" element={<About />} /> </Routes> </BrowserRouter> ); }

[!NOTE] Older React Router projects may import these APIs from react-router-dom. Check the version used by the project before copying router imports.


Route Parameters

Concept:

<Route path="/users/:userId" element={<UserPage />} />

Inside the route:

const { userId } = useParams();

const navigate = useNavigate(); navigate("/dashboard");

Replace history entry:

navigate("/login", { replace: true });

Prefer router links for client navigation:

<Link to="/settings"> Settings </Link>

instead of forcing a full page reload with:

<a href="/settings"> Settings </a>

when the route belongs to the client-side router.


Nested Routes

Conceptually:

<Route path="/dashboard" element={<DashboardLayout />} > <Route index element={<Overview />} /> <Route path="settings" element={<Settings />} /> </Route>

Parent:

function DashboardLayout() { return ( <> <DashboardNav /> <Outlet /> </> ); }

Data Routers

Modern React Router can define route objects:

const router = createBrowserRouter([ { path: "/", Component: Home }, { path: "/users/:id", Component: User } ]);

Then provide the router:

<RouterProvider router={router} />

Data routers can integrate concepts such as:

loaders actions navigation state error boundaries revalidation route-level data

🧭 Navigation State

Example concept:

const navigation = useNavigation(); const loading = navigation.state === "loading";

Useful for global navigation indicators:

{loading && <ProgressBar />}

🎨 Styling

React does not prescribe one styling approach.

Common options:

plain CSS CSS Modules utility CSS CSS-in-JS component libraries design systems inline styles

Plain CSS

import "./Button.css"; function Button() { return ( <button className="button"> Save </button> ); }

CSS Modules

import styles from "./Button.module.css"; function Button() { return ( <button className={styles.button}> Save </button> ); }

Conditional Classes

<div className={ active ? "card card--active" : "card" } />

Template literal:

<div className={`card ${ active ? "card--active" : "" }`} />

Inline Styles

<div style={{ color: "red", fontSize: 20, marginTop: 8 }} > Hello </div>

Properties use camelCase:

font-size → fontSize background-color → backgroundColor margin-top → marginTop

Numbers often mean pixels where appropriate:

style={{ width: 200 }}

CSS Custom Properties

<div style={{ "--accent": color }} > ... </div>

CSS:

.card { color: var(--accent); }

🖼️ Images

Import bundled image:

import avatar from "./avatar.png"; <img src={avatar} alt="Ada Lovelace" />

Remote:

<img src={user.avatarUrl} alt={`${user.name}'s avatar`} />

Always consider useful alt text.

Decorative image:

<img src={decoration} alt="" />

♿ Accessibility

React doesn't replace normal web accessibility rules.

Use semantic HTML:

<button onClick={handleClick}> Save </button>

Prefer that over:

<div onClick={handleClick}> Save </div>

Labels

<label htmlFor="email"> Email </label> <input id="email" type="email" />

Accessible Buttons

<button aria-label="Close dialog" onClick={onClose} > × </button>

ARIA

Use ARIA when semantic HTML alone cannot express the behavior.

<button aria-expanded={open} aria-controls="menu" > Menu </button>

[!TIP] Prefer native semantic HTML before adding ARIA.


🧑‍💻 Keyboard Events

function handleKeyDown(event) { if (event.key === "Enter") { submit(); } if (event.key === "Escape") { close(); } }
<input onKeyDown={handleKeyDown} />

Common values:

Enter Escape ArrowUp ArrowDown ArrowLeft ArrowRight Tab Space / " "

🪟 Browser APIs

Use browser APIs in event handlers or Effects where appropriate.

Example:

function CopyButton({ text }) { async function handleCopy() { await navigator.clipboard.writeText(text); } return ( <button onClick={handleCopy}> Copy </button> ); }

localStorage

useEffect(() => { localStorage.setItem( "theme", theme ); }, [theme]);

Initialize:

const [theme, setTheme] = useState(() => { return ( localStorage.getItem("theme") ?? "light" ); });

In server-rendered environments, browser globals may not exist during server rendering, so browser-only access needs to respect the framework's execution model.


🔄 External Stores

For data stored outside React that needs subscriptions, React provides useSyncExternalStore.

Concept:

const value = useSyncExternalStore( store.subscribe, store.getSnapshot );

Useful for libraries integrating React with:

external state stores browser APIs shared mutable stores subscription-based systems

Most application code consumes this through a library rather than implementing it manually.


🧪 Testing

A common philosophy:

Test what the user can observe.

Using Testing Library:

import { render, screen } from "@testing-library/react"; import App from "./App"; test("renders greeting", () => { render(<App />); expect( screen.getByText(/hello/i) ).toBeInTheDocument(); });

User Interaction

Conceptually:

import userEvent from "@testing-library/user-event"; test("increments count", async () => { const user = userEvent.setup(); render(<Counter />); await user.click( screen.getByRole( "button", { name: /increment/i } ) ); expect( screen.getByText("1") ).toBeInTheDocument(); });

Prefer Accessible Queries

Good:

screen.getByRole( "button", { name: /save/i } );

Also useful:

getByLabelText() getByText() getByPlaceholderText() getByAltText() findByRole() queryByRole()

getBy vs queryBy vs findBy

getBy* → expected to exist immediately → throws if missing queryBy* → checking absence → returns null if missing findBy* → asynchronous appearance → returns Promise

Example:

const alert = await screen.findByRole("alert");

🧯 Testing Async UI

test("loads user", async () => { render(<UserPage />); expect( screen.getByText(/loading/i) ).toBeInTheDocument(); expect( await screen.findByText( "Ada Lovelace" ) ).toBeInTheDocument(); });

🧪 Testing Hooks Through Behavior

Rather than testing implementation details such as:

Was useState called? Was useEffect called?

test observable behavior:

Does clicking Save submit? Does loading appear? Does the error message show? Does the list update?

🧰 React DevTools

Useful for:

inspect component tree inspect props inspect state inspect context profile rendering find unnecessary re-renders

🐞 Debugging Renders

function Component({ user }) { console.log( "render", user ); return <div>{user.name}</div>; }

Effects:

useEffect(() => { console.log( "effect", dependency ); }, [dependency]);

Cleanup:

useEffect(() => { console.log("setup"); return () => { console.log("cleanup"); }; }, []);

🔍 Why Did This Render?

Common reasons:

its state changed its parent rendered its context value changed an external store snapshot changed its key changed and it remounted

A parent rendering does not necessarily mean expensive DOM work occurred.

React may render components but commit little or no DOM change.


🧹 Cleanup Checklist

Effects that often require cleanup:

setInterval setTimeout in some flows event listeners WebSocket connections subscriptions observers third-party widgets AbortController requests

Example:

useEffect(() => { const socket = new WebSocket(url); return () => { socket.close(); }; }, [url]);

🧩 Common Patterns

Toggle

const [open, setOpen] = useState(false); <button onClick={() => setOpen(open => !open) } > Toggle </button>

Select One Item

const [selectedId, setSelectedId] = useState(null); items.map(item => ( <button key={item.id} onClick={() => setSelectedId(item.id) } > {item.name} </button> ));

Add Todo

setTodos(todos => [ ...todos, { id: crypto.randomUUID(), text, done: false } ]);

Delete Todo

setTodos(todos => todos.filter( todo => todo.id !== id ) );

Update Todo

setTodos(todos => todos.map(todo => todo.id === id ? { ...todo, text: newText } : todo ) );

Toggle Todo

setTodos(todos => todos.map(todo => todo.id === id ? { ...todo, done: !todo.done } : todo ) );

Search Filter

const visibleItems = items.filter(item => item.name .toLowerCase() .includes( query.toLowerCase() ) );

Sort

const sortedItems = items.toSorted( (a, b) => a.name.localeCompare(b.name) );

Group Data

const groups = Object.groupBy( users, user => user.role );

Where supported.


Unique Values

const categories = [ ...new Set( products.map( product => product.category ) ) ];

📚 Common Hook Reference

HookPurpose
useStateLocal component state
useReducerStructured state transitions
useContextRead shared context
useRefPersistent mutable value / DOM ref
useEffectSynchronize with external systems
useLayoutEffectPre-paint layout synchronization
useEffectEventEvent-like logic used by Effects
useMemoCache calculation
useCallbackCache function reference
useTransitionMark non-urgent updates
useDeferredValueDefer less-urgent rendering
useIdStable accessibility IDs
useImperativeHandleCustomize exposed ref API
useSyncExternalStoreSubscribe to external stores
useActionStateState associated with Actions
useOptimisticOptimistic Action state
useRead supported resources

🧾 React DOM Reference

Common client APIs:

import { createRoot, hydrateRoot } from "react-dom/client";

Common DOM APIs:

import { createPortal, flushSync } from "react-dom";

Forms:

import { useFormStatus } from "react-dom";

💧 Hydration

For HTML already rendered by React on the server, use hydration rather than replacing it with a new root.

Concept:

import { hydrateRoot } from "react-dom/client"; hydrateRoot( document.getElementById("root"), <App /> );

Hydration connects React behavior to existing server-rendered HTML.


Hydration Mismatches

Server and initial client rendering should generally agree.

Potential problems:

function Component() { return ( <p> {Math.random()} </p> ); }

Server value:

0.123

Client initial value:

0.782

Mismatch.

Other common causes:

Date.now() browser-only globals during server render different locale output invalid HTML nesting different conditional branches random values

🚪 createPortal

Common modal pattern:

function Modal({ open, children }) { if (!open) { return null; } return createPortal( <div className="backdrop"> <div className="modal"> {children} </div> </div>, document.body ); }

🧱 Error Handling Patterns

Event Error

async function handleSave() { try { setSaving(true); await save(); setError(null); } catch (error) { setError(error); } finally { setSaving(false); } }

Error UI

{error && ( <p role="alert"> {error.message} </p> )}

Retry

<button onClick={loadData}> Try again </button>

⏱️ Debouncing Input

A simple Effect-based debounce:

function Search() { const [query, setQuery] = useState(""); const [ debouncedQuery, setDebouncedQuery ] = useState(""); useEffect(() => { const id = setTimeout(() => { setDebouncedQuery(query); }, 300); return () => clearTimeout(id); }, [query]); // fetch/search using debouncedQuery }

For expensive rendering rather than network debounce, useDeferredValue may be more appropriate.


📡 Subscriptions

useEffect(() => { const unsubscribe = store.subscribe(value => { setValue(value); }); return unsubscribe; }, []);

If integrating a true external store, consider useSyncExternalStore.


🧮 Expensive Calculations

Without memoization:

const result = expensiveCalculation(data);

Memoized:

const result = useMemo( () => expensiveCalculation(data), [data] );

Only optimize when it matters.


💤 Lazy Initial Values

Good:

const [todos, setTodos] = useState(() => loadTodosFromStorage() );

Instead of doing expensive initialization on each render expression.


🏷️ Dynamic Element Types

function Heading({ level, children }) { const Tag = `h${level}`; return <Tag>{children}</Tag>; }

For known sets, explicit mappings can be safer:

const tags = { 1: "h1", 2: "h2", 3: "h3" }; const Tag = tags[level] ?? "p";

🧠 Component Identity

These are different component types:

function A() {} function B() {}

Switching between them at the same position resets state:

{mode === "a" ? <A /> : <B /> }

Keys can also control identity:

<Editor key={documentId} />

🚫 Don't Define Components Inside Components

Avoid:

function Parent() { function Child() { const [count, setCount] = useState(0); return <div>{count}</div>; } return <Child />; }

Child is recreated as a new component type during every parent render, which can reset its state.

Prefer:

function Child() { const [count, setCount] = useState(0); return <div>{count}</div>; } function Parent() { return <Child />; }

🎛️ Boolean Props

<Button disabled />

is equivalent to:

<Button disabled={true} />

Dynamic:

<Button disabled={ loading || !formValid } />

🌊 Prop Spreading

Useful:

function Input(props) { return <input {...props} />; }

More controlled:

function Input({ label, ...inputProps }) { return ( <label> {label} <input {...inputProps} /> </label> ); }

Be mindful of accidentally forwarding internal props to DOM elements.


🧪 Conditional Props

<Button {...(loading ? { disabled: true } : {})} > Save </Button>

Usually simpler:

<Button disabled={loading}> Save </Button>

📎 Passing Components as Props

function EmptyState({ icon: Icon }) { return ( <div> <Icon /> <p>No results</p> </div> ); }

Usage:

<EmptyState icon={SearchIcon} />

📦 Passing JSX as Props

<Layout sidebar={<Sidebar />} footer={<Footer />} > <Dashboard /> </Layout>

🧠 Callback Naming

Common convention:

onSave onClose onSelect onChange onDelete onSubmit

Handler implementation:

handleSave handleClose handleSelect handleChange handleDelete handleSubmit

Example:

function Dialog({ onClose }) { function handleClose() { onClose(); } // ... }

🔒 Stale Closures

Functions capture values from the render where they were created.

Example:

function Counter() { const [count, setCount] = useState(0); function showLater() { setTimeout(() => { console.log(count); }, 3000); } // ... }

The timeout callback sees the count from that render.

If updating based on latest state, use a functional updater:

setCount(c => c + 1);

Refs may be appropriate when an asynchronous callback genuinely needs the latest mutable value without re-rendering.


🧯 Effect Dependency Gotchas

Bad:

useEffect(() => { fetchUser(userId); }, []); // userId omitted

If the Effect reads userId, it generally belongs in the dependency list:

useEffect(() => { fetchUser(userId); }, [userId]);

Don't silence dependency warnings blindly.

Instead consider:

Does this need to be an Effect? Can logic move into an event handler? Can a value be calculated during render? Should the code move inside the Effect? Should part become an Effect Event?

🔁 Infinite Effect Loop

Example:

useEffect(() => { setCount(count + 1); }, [count]);

Sequence:

count changes → Effect runs → Effect changes count → render → Effect runs → ...

Usually this means the state update doesn't belong in the Effect or the design needs restructuring.


🧪 Object Dependencies

This creates a new object on every render:

const options = { roomId }; useEffect(() => { connect(options); }, [options]);

So the dependency changes every render.

Often better:

useEffect(() => { const options = { roomId }; connect(options); }, [roomId]);

📈 Performance Tips

Usually Good

  • Keep state near where it is used.
  • Avoid unnecessary global state.
  • Keep render logic pure.
  • Remove unnecessary Effects.
  • Split very expensive subtrees.
  • Virtualize huge lists when necessary.
  • Lazy-load large routes/features.
  • Run independent requests concurrently.
  • Use Suspense where your stack supports it.
  • Profile before memoizing.
  • Keep context values focused.
  • Use stable keys.

Don't Optimize Just Because You See

() => handleClick()

or:

const object = {};

Creating functions/objects during render is normal JavaScript.

Only stabilize them when identity actually affects an optimization or Effect.


🧠 React Compiler

Modern React tooling may use React Compiler to automatically optimize some component and Hook behavior.

The practical lesson remains:

write pure components follow the Rules of Hooks avoid mutation keep Effects correct

Don't make code harder to understand purely to "help React optimize" unless profiling proves it necessary.


📦 Folder Structure Example

Small project:

src/ ├── main.jsx ├── App.jsx ├── App.css ├── components/ │ ├── Button.jsx │ ├── Header.jsx │ └── UserCard.jsx ├── hooks/ │ └── useOnlineStatus.js ├── utils/ │ └── formatDate.js └── assets/ └── logo.svg

Larger feature-based project:

src/ ├── app/ │ ├── App.jsx │ └── router.jsx │ ├── features/ │ ├── auth/ │ │ ├── components/ │ │ ├── hooks/ │ │ └── api/ │ │ │ └── todos/ │ ├── components/ │ ├── hooks/ │ └── api/ │ ├── components/ ├── hooks/ ├── lib/ ├── utils/ └── styles/

No single folder structure is universally correct.

Organize around how the application is maintained.


📤 Imports & Exports

Named Export

export function Button() { return <button>Click</button>; }

Import:

import { Button } from "./Button.jsx";

Default Export

export default function App() { return <main>Hello</main>; }

Import:

import App from "./App.jsx";

Multiple Named Exports

export function Button() {} export function IconButton() {} export function LinkButton() {}
import { Button, IconButton, LinkButton } from "./buttons.jsx";

🧾 Common React Patterns at a Glance

Render Component

<Component />

Pass String Prop

<Component name="Ada" />

Pass Expression

<Component count={count} />

Pass Boolean

<Component disabled />

Pass Callback

<Component onSave={handleSave} />

Pass Children

<Component> <p>Hello</p> </Component>

Conditional

{condition && <Component />}

Either/Or

{condition ? <A /> : <B />}

Map List

items.map(item => ( <Item key={item.id} item={item} /> ))

State

const [ value, setValue ] = useState(initial);

Update from Previous State

setValue( previous => next(previous) );

Effect

useEffect(() => { // synchronize return () => { // cleanup }; }, [dependencies]);

Ref

const ref = useRef(null);

Context

const value = useContext(Context);

Reducer

const [ state, dispatch ] = useReducer( reducer, initialState );

🧑‍🎓 Common Gotchas

Mutating State

Bad:

items.push(newItem); setItems(items);

Good:

setItems([ ...items, newItem ]);

Using Unstable Keys

Bad:

<Item key={Math.random()} />

This gives React a new identity on every render.

Use:

<Item key={item.id} />

Calling Handler During Render

Bad:

<button onClick={deleteItem(id)} >

Good:

<button onClick={() => deleteItem(id) } >

Missing Effect Cleanup

Bad:

useEffect(() => { window.addEventListener( "resize", handleResize ); }, []);

Good:

useEffect(() => { window.addEventListener( "resize", handleResize ); return () => { window.removeEventListener( "resize", handleResize ); }; }, []);

Updating State During Render

Bad:

function Component() { const [count, setCount] = useState(0); setCount(1); return <div>{count}</div>; }

This can create a render loop.

State updates usually belong in:

event handlers Effects where synchronization requires them Actions external subscription callbacks

Using useEffect for Derived Data

Bad:

useEffect(() => { setFiltered( items.filter(filterFn) ); }, [items]);

Better:

const filtered = items.filter(filterFn);

Memoize only if necessary:

const filtered = useMemo( () => items.filter(filterFn), [items, filterFn] );

Async Effect Callback

Avoid making the Effect callback itself async:

useEffect(async () => { // ❌ returns Promise instead of cleanup }, []);

Instead:

useEffect(() => { async function load() { // ... } load(); }, []);

await setState(...)

This doesn't work as a way to wait for a render:

await setCount(5);

State setters do not return a Promise representing completion of the render.


Reading New State Immediately

setCount(count + 1); console.log(count);

count is still the current render's snapshot.


Index as Key

Potentially problematic:

items.map((item, index) => ( <Input key={index} value={item.name} /> ));

When the list reorders, component state may appear attached to the wrong item.


Random Values During Render

Potential hydration problem:

return ( <div> {Math.random()} </div> );

Also breaks purity expectations if used for logic.

Generate IDs when creating data:

const newTodo = { id: crypto.randomUUID(), text };

💡 Debugging Checklist

When React behaves strangely:

  1. Check browser console errors.
  2. Inspect current props.
  3. Inspect current state.
  4. Check whether you're mutating state.
  5. Check list keys.
  6. Check component identity.
  7. Check Effect dependencies.
  8. Check Effect cleanup.
  9. Check for an Effect loop.
  10. Check whether an event handler is being called during render.
  11. Check controlled input value/checked.
  12. Check whether data can be null or undefined.
  13. Check asynchronous race conditions.
  14. Check server/client execution boundaries.
  15. Check hydration warnings.
  16. Inspect the component using React DevTools.
  17. Profile before assuming re-renders are expensive.

🧠 Error Translator

Objects are not valid as a React child

Bad:

<div>{user}</div>

where:

user = { name: "Ada" };

Render a property:

<div>{user.name}</div>

Each child in a list should have a unique "key" prop

Add stable keys:

items.map(item => ( <Item key={item.id} item={item} /> ));

Too many re-renders

Often caused by state updates during render:

setCount(count + 1);

or immediately calling a handler:

onClick={setCount(count + 1)}

Use:

onClick={() => setCount(count + 1) }

Cannot update a component while rendering a different component

Usually means a state update is happening during another component's render.

Move the update to an event, Effect, or appropriate external callback.


Invalid hook call

Common causes:

Hook called outside a component/custom Hook Hook called conditionally multiple incompatible React copies React/react-dom version mismatch

Hydration Warning

Typically means server HTML differs from the client's initial render.

Check:

dates random values browser-only conditions locale formatting invalid HTML changing external data

🧠 Lifecycle Mental Model

Function component lifecycle:

Mount ↓ Render ↓ Commit ↓ Effects setup State/props/context change ↓ Render ↓ Commit ↓ old Effect cleanup if needed ↓ new Effect setup if needed Unmount ↓ Effect cleanup

🔁 Effect Lifecycle Mental Model

For:

useEffect(() => { connect(roomId); return () => { disconnect(roomId); }; }, [roomId]);

Think:

roomId = 1 connect(1) roomId changes to 2 disconnect(1) connect(2) component removed disconnect(2)

Effects are about synchronization, not simply "component lifecycle callbacks."


⚡ Rendering Optimization Checklist

Before adding memo, useMemo, or useCallback:

Is the component actually slow? ↓ Profile it. Is an Effect creating unnecessary state updates? ↓ Fix the Effect. Is state too high in the tree? ↓ Move it closer to where it's used. Is a context value changing too often? ↓ Split/restructure context. Is a huge list rendered? ↓ Consider virtualization. Is expensive code recalculated? ↓ Consider memoization. Is a large feature loaded eagerly? ↓ Consider lazy loading.

🧩 State Tool Decision Tree

Need a value that affects rendering? │ ├─ No │ └─ useRef │ └─ Yes │ ├─ Simple local value? │ └─ useState │ ├─ Complex transitions? │ └─ useReducer │ ├─ Shared through a subtree? │ └─ Context │ ├─ External subscription? │ └─ useSyncExternalStore │ ├─ Async Action result? │ └─ useActionState │ └─ Temporary optimistic value? └─ useOptimistic

🔁 Rendering Tool Decision Tree

Need to display something? │ ├─ Transform values │ └─ calculate during render │ ├─ Conditionally display │ └─ if / ? : / && │ ├─ Render collection │ └─ map() + stable key │ ├─ Show loading boundary │ └─ Suspense │ ├─ Lazy-load code │ └─ lazy() + Suspense │ └─ Render elsewhere in DOM └─ createPortal()

⚙️ Side-Effect Decision Tree

Something needs to happen. │ ├─ User explicitly did something? │ └─ event handler / Action │ ├─ Can value be calculated during render? │ └─ calculate it; no Effect │ ├─ Need to synchronize with external system? │ └─ useEffect │ ├─ Need layout measurement before paint? │ └─ useLayoutEffect │ └─ External subscription? └─ useSyncExternalStore

📊 Props vs State vs Ref vs Context

ToolPurposeTriggers Render When Changed?
PropsParent → child dataParent rendering may update child
StateComponent-owned UI data✅
RefPersistent mutable value❌
ContextShared subtree data✅ consumers
ReducerStructured state✅
External StoreShared external data✅ subscribed consumers

📊 Common Rendering APIs

APIPurpose
<Suspense>Loading boundary
<Fragment> / <>Group without DOM wrapper
<Activity>Control/preserve UI activity
lazy()Lazy-load component module
memo()Memoize component rendering
createPortal()Render into another DOM node

📊 Async APIs

APIPurpose
useTransitionTrack non-urgent transition
startTransitionMark update as transition
useDeferredValueDefer less-urgent value
useActionStateManage Action result state
useOptimisticShow optimistic state
useFormStatusRead parent form status
use()Read supported async resource
<Suspense>Display fallback while suspended

📊 State Update Cheat Sheet

Number

setCount(c => c + 1);

Boolean

setOpen(open => !open);

String

setName("Ada");

Object Field

setUser(user => ({ ...user, name: "Ada" }));

Nested Object

setUser(user => ({ ...user, address: { ...user.address, city: "Seoul" } }));

Add Array Item

setItems(items => [ ...items, item ]);

Remove Array Item

setItems(items => items.filter( item => item.id !== id ) );

Update Array Item

setItems(items => items.map(item => item.id === id ? { ...item, value } : item ) );

📊 Conditional Rendering Cheat Sheet

NeedPattern
Render if true{condition && <A />}
A or B{condition ? <A /> : <B />}
Complex branchif (...) return ...
Render nothingreturn null
Choose componentconst Component = condition ? A : B

📊 List Cheat Sheet

Transform data:

items.map(item => ( <Item key={item.id} item={item} /> ));

Filter first:

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

Sort without mutation:

items .toSorted( (a, b) => a.name.localeCompare( b.name ) ) .map(item => ( <Item key={item.id} item={item} /> ));

📊 Hook Selection Cheat Sheet

NeedHook/API
Local changing UI valueuseState
Complex state machine-ish logicuseReducer
Shared valueuseContext
DOM nodeuseRef
Persistent value without re-renderuseRef
External synchronizationuseEffect
Layout measurementuseLayoutEffect
Expensive calculation cacheuseMemo
Stable callback for optimizationuseCallback
Low-priority updateuseTransition
Defer expensive UIuseDeferredValue
Accessibility IDuseId
External storeuseSyncExternalStore
Action resultuseActionState
Optimistic Action feedbackuseOptimistic
Async resourceuse

⚡ Handy Snippets

Toggle

setOpen(open => !open);

Increment

setCount(count => count + 1);

Previous State

setState(previous => { return nextState; });

Optional Component

{user && ( <Welcome name={user.name} /> )}

Map Array

items.map(item => ( <li key={item.id}> {item.name} </li> ))

Filter + Map

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

Loading

if (loading) { return <Spinner />; }

Error

if (error) { return ( <p role="alert"> {error.message} </p> ); }

Empty State

if (items.length === 0) { return <EmptyState />; }

Default Export

export default function App() { return <main>Hello</main>; }

Lazy Component

const Page = lazy(() => import("./Page.jsx") );

Focus Element

inputRef.current?.focus();

Reset Form State

setForm(initialForm);

Reset Component

<Form key={formId} />

💡 React Rules of Thumb

  1. Components should be pure during render.
  2. Prefer function components and Hooks for modern code.
  3. Use const by default.
  4. Pass data down through props.
  5. Pass callbacks down when children need to request changes.
  6. Keep state as local as practical.
  7. Lift state only when multiple components genuinely need it.
  8. Don't mutate state.
  9. Use stable keys based on data identity.
  10. Don't store derived values in state unnecessarily.
  11. Don't use Effects for ordinary calculations.
  12. Use Effects to synchronize with external systems.
  13. Clean up subscriptions, timers, and connections.
  14. Include legitimate Effect dependencies.
  15. Use functional state updates when next state depends on previous state.
  16. Treat state as a snapshot.
  17. Use refs for values that don't affect rendering.
  18. Use context intentionally—not as a replacement for every prop.
  19. Profile before adding memoization.
  20. Use Suspense-compatible architecture when your stack supports it.
  21. Use useTransition for non-urgent UI transitions.
  22. Use optimistic UI when immediate feedback improves the experience.
  23. Prefer semantic HTML and accessible interactions.
  24. Validate and authorize data on the server.
  25. Keep server secrets out of Client Components.
  26. Test behavior rather than implementation details.
  27. Use Error Boundaries around meaningful failure boundaries.
  28. Avoid creating state simply to mirror other state.
  29. Understand component identity when debugging state resets.
  30. When React feels mysterious, inspect props, state, identity, and Effects first.

🧠 The 20% That Solves 80%

import { useEffect, useRef, useState } from "react"; function TodoApp() { const [todos, setTodos] = useState([]); const [text, setText] = useState(""); const inputRef = useRef(null); function addTodo(event) { event.preventDefault(); const trimmed = text.trim(); if (!trimmed) { return; } setTodos(todos => [ ...todos, { id: crypto.randomUUID(), text: trimmed, done: false } ]); setText(""); inputRef.current?.focus(); } function toggleTodo(id) { setTodos(todos => todos.map(todo => todo.id === id ? { ...todo, done: !todo.done } : todo ) ); } function deleteTodo(id) { setTodos(todos => todos.filter( todo => todo.id !== id ) ); } useEffect(() => { document.title = `${todos.length} todos`; }, [todos.length]); const remaining = todos.filter( todo => !todo.done ).length; return ( <main> <h2>Todos</h2> <form onSubmit={addTodo}> <input ref={inputRef} value={text} onChange={ event => setText(event.target.value) } placeholder="Add todo" /> <button type="submit"> Add </button> </form> <p> {remaining} remaining </p> {todos.length === 0 ? ( <p>No todos yet.</p> ) : ( <ul> {todos.map(todo => ( <li key={todo.id}> <label> <input type="checkbox" checked={todo.done} onChange={() => toggleTodo(todo.id) } /> {todo.text} </label> <button onClick={() => deleteTodo(todo.id) } > Delete </button> </li> ))} </ul> )} </main> ); }

This small example demonstrates most everyday React fundamentals:

component JSX state controlled input events functional state updates immutable arrays list rendering keys conditional rendering derived values refs effects cleanup mindset

⚛️ TL;DR Mind Map

AreaExampleRemember
Componentfunction App() {}UI building block
JSX<div>{value}</div>JavaScript expressions use {}
Props<Card user={user} />Parent → child data
Children{children}Composition
StateuseState()Local changing UI data
UpdatesetCount(c => c + 1)Use updater for previous state
Objects{ ...obj }Don't mutate state
Arrays.map() / .filter()Immutable updates
EventsonClick={handler}Pass function, don't call immediately
Listskey={item.id}Stable identity
Formsvalue + onChangeControlled input
RefuseRef()DOM / mutable non-UI value
EffectuseEffect()External synchronization
ContextuseContext()Shared subtree data
ReduceruseReducer()Complex state transitions
Custom HookuseSomething()Reusable stateful logic
MemouseMemo()Cache expensive calculation
CallbackuseCallback()Stable callback for optimization
TransitionuseTransition()Non-urgent update
DeferreduseDeferredValue()Defer expensive rendering
Suspense<Suspense>Loading boundary
Lazylazy()Code splitting
Async Resourceuse()Read supported resource
ActionsuseActionState()Async Action state
Optimistic UIuseOptimistic()Immediate feedback
Form StatususeFormStatus()Pending form state
PortalcreatePortal()Render elsewhere in DOM
Server Componentasync function Page()Server-rendered component logic
Client Boundary"use client"Interactive client component
Server Function"use server"Server-side callable function
Router<Routes>Navigation
Testingscreen.getByRole()Test observable behavior
ErrorsError BoundaryIsolate rendering failures
PerformanceProfiler firstMeasure before optimizing

🧠 Final Mental Model

When writing React, think in this order:

1. What should the UI look like for these props/state? ↓ 2. What is the minimal state I actually need? ↓ 3. Can anything be derived instead of stored? ↓ 4. Which component should own the state? ↓ 5. What events can change that state? ↓ 6. Do I need to synchronize with an external system? ↓ 7. If yes → Effect / external-store API ↓ 8. Is asynchronous UI involved? ↓ 9. Consider Suspense / Actions / Transitions ↓ 10. Is there a measured performance problem? ↓ 11. Only then optimize

[!TIP] If a React component is getting difficult to understand, check these four things first:

  1. Is too much state being stored?
  2. Is state owned too high or too low in the tree?
  3. Is an Effect being used for logic that belongs in rendering or an event handler?
  4. Is component identity changing because of keys or component definitions?

Those explain a very large percentage of everyday React bugs.


🔗 See Also

Backlinks (3)
Categories (0)

    No categories assigned to this page.

Edit Level

> Signed In Users