React (software)/Cheat sheet
Last edited by disfordave on 27/08/2026, 06:04:37 UTC
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
StrictModecan 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;
Group Related State
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
| Feature | State | Ref |
|---|---|---|
| Persists between renders | ✅ | ✅ |
| Updating triggers render | ✅ | ❌ |
| Used to display UI | ✅ | Usually ❌ |
| Good for DOM nodes | ❌ | ✅ |
| Good for timer IDs | Sometimes | ✅ |
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
StrictModemay 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:
- At the top level of a component.
- 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 likeuseState,useEffect, anduseMemofollow 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();
Navigation
const navigate = useNavigate(); navigate("/dashboard");
Replace history entry:
navigate("/login", { replace: true });
Links
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
| Hook | Purpose |
|---|---|
useState | Local component state |
useReducer | Structured state transitions |
useContext | Read shared context |
useRef | Persistent mutable value / DOM ref |
useEffect | Synchronize with external systems |
useLayoutEffect | Pre-paint layout synchronization |
useEffectEvent | Event-like logic used by Effects |
useMemo | Cache calculation |
useCallback | Cache function reference |
useTransition | Mark non-urgent updates |
useDeferredValue | Defer less-urgent rendering |
useId | Stable accessibility IDs |
useImperativeHandle | Customize exposed ref API |
useSyncExternalStore | Subscribe to external stores |
useActionState | State associated with Actions |
useOptimistic | Optimistic Action state |
use | Read 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:
- Check browser console errors.
- Inspect current props.
- Inspect current state.
- Check whether you're mutating state.
- Check list keys.
- Check component identity.
- Check Effect dependencies.
- Check Effect cleanup.
- Check for an Effect loop.
- Check whether an event handler is being called during render.
- Check controlled input
value/checked. - Check whether data can be
nullorundefined. - Check asynchronous race conditions.
- Check server/client execution boundaries.
- Check hydration warnings.
- Inspect the component using React DevTools.
- 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
| Tool | Purpose | Triggers Render When Changed? |
|---|---|---|
| Props | Parent → child data | Parent rendering may update child |
| State | Component-owned UI data | ✅ |
| Ref | Persistent mutable value | ❌ |
| Context | Shared subtree data | ✅ consumers |
| Reducer | Structured state | ✅ |
| External Store | Shared external data | ✅ subscribed consumers |
📊 Common Rendering APIs
| API | Purpose |
|---|---|
<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
| API | Purpose |
|---|---|
useTransition | Track non-urgent transition |
startTransition | Mark update as transition |
useDeferredValue | Defer less-urgent value |
useActionState | Manage Action result state |
useOptimistic | Show optimistic state |
useFormStatus | Read 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
| Need | Pattern |
|---|---|
| Render if true | {condition && <A />} |
| A or B | {condition ? <A /> : <B />} |
| Complex branch | if (...) return ... |
| Render nothing | return null |
| Choose component | const 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
| Need | Hook/API |
|---|---|
| Local changing UI value | useState |
| Complex state machine-ish logic | useReducer |
| Shared value | useContext |
| DOM node | useRef |
| Persistent value without re-render | useRef |
| External synchronization | useEffect |
| Layout measurement | useLayoutEffect |
| Expensive calculation cache | useMemo |
| Stable callback for optimization | useCallback |
| Low-priority update | useTransition |
| Defer expensive UI | useDeferredValue |
| Accessibility ID | useId |
| External store | useSyncExternalStore |
| Action result | useActionState |
| Optimistic Action feedback | useOptimistic |
| Async resource | use |
⚡ 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
- Components should be pure during render.
- Prefer function components and Hooks for modern code.
- Use
constby default. - Pass data down through props.
- Pass callbacks down when children need to request changes.
- Keep state as local as practical.
- Lift state only when multiple components genuinely need it.
- Don't mutate state.
- Use stable keys based on data identity.
- Don't store derived values in state unnecessarily.
- Don't use Effects for ordinary calculations.
- Use Effects to synchronize with external systems.
- Clean up subscriptions, timers, and connections.
- Include legitimate Effect dependencies.
- Use functional state updates when next state depends on previous state.
- Treat state as a snapshot.
- Use refs for values that don't affect rendering.
- Use context intentionally—not as a replacement for every prop.
- Profile before adding memoization.
- Use Suspense-compatible architecture when your stack supports it.
- Use
useTransitionfor non-urgent UI transitions. - Use optimistic UI when immediate feedback improves the experience.
- Prefer semantic HTML and accessible interactions.
- Validate and authorize data on the server.
- Keep server secrets out of Client Components.
- Test behavior rather than implementation details.
- Use Error Boundaries around meaningful failure boundaries.
- Avoid creating state simply to mirror other state.
- Understand component identity when debugging state resets.
- 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
| Area | Example | Remember |
|---|---|---|
| Component | function App() {} | UI building block |
| JSX | <div>{value}</div> | JavaScript expressions use {} |
| Props | <Card user={user} /> | Parent → child data |
| Children | {children} | Composition |
| State | useState() | Local changing UI data |
| Update | setCount(c => c + 1) | Use updater for previous state |
| Objects | { ...obj } | Don't mutate state |
| Arrays | .map() / .filter() | Immutable updates |
| Events | onClick={handler} | Pass function, don't call immediately |
| Lists | key={item.id} | Stable identity |
| Forms | value + onChange | Controlled input |
| Ref | useRef() | DOM / mutable non-UI value |
| Effect | useEffect() | External synchronization |
| Context | useContext() | Shared subtree data |
| Reducer | useReducer() | Complex state transitions |
| Custom Hook | useSomething() | Reusable stateful logic |
| Memo | useMemo() | Cache expensive calculation |
| Callback | useCallback() | Stable callback for optimization |
| Transition | useTransition() | Non-urgent update |
| Deferred | useDeferredValue() | Defer expensive rendering |
| Suspense | <Suspense> | Loading boundary |
| Lazy | lazy() | Code splitting |
| Async Resource | use() | Read supported resource |
| Actions | useActionState() | Async Action state |
| Optimistic UI | useOptimistic() | Immediate feedback |
| Form Status | useFormStatus() | Pending form state |
| Portal | createPortal() | Render elsewhere in DOM |
| Server Component | async function Page() | Server-rendered component logic |
| Client Boundary | "use client" | Interactive client component |
| Server Function | "use server" | Server-side callable function |
| Router | <Routes> | Navigation |
| Testing | screen.getByRole() | Test observable behavior |
| Errors | Error Boundary | Isolate rendering failures |
| Performance | Profiler first | Measure 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:
- Is too much state being stored?
- Is state owned too high or too low in the tree?
- Is an Effect being used for logic that belongs in rendering or an event handler?
- Is component identity changing because of keys or component definitions?
Those explain a very large percentage of everyday React bugs.
🔗 See Also
Backlinks (3)
- General
- User
No backlinks yet.
- Redirects
- Media
No backlinks yet.
- Categories
No backlinks yet.
Categories (0)
No categories assigned to this page.
Edit Level
> Signed In Users
Latest on Lounge
Join the conversation about the 'React (software)/Cheat sheet' article →
No comments yet. Be the first to comment!