Fundamentals — Cheat Sheet
React · 7 topics. Download the PDF or the Instagram carousel and share it.
The Mental Model — UI as a Function of State
You describe what the screen should look like for a given state, and React works out what to change. You never write the change itself — that inversion is the whole framework.
- ✓React is declarative: you describe the UI for a given state and React performs the DOM changes
- ✓Reaching for the DOM directly is usually a sign that a piece of state is missing
- ✓Data flows down through props and changes flow up through callbacks — there is no two-way binding
- ✓Each piece of state has exactly one owner, which is what makes changes traceable
- ✓React is only the rendering layer; routing, data fetching and the build come from the surrounding stack
// Imperative — you perform each transition yourself
function showResults(results) {
spinner.style.display = 'none'
errorBox.style.display = 'none'
list.innerHTML = ''
results.forEach(r => list.append(makeRow(r)))
emptyState.style.display = results.length ? 'none' : 'block'
}
// Every new state means auditing every branch again.
// Declarative — you describe the destination for each state
function Results({ status, results, error }) {
if (status === 'loading') return <Spinner />
if (status === 'error') return <ErrorBox message={error} />
if (results.length === 0) return <EmptyState />
return <ul>{results.map(r => <Row key={r.id} {...r} />)}</ul>
}
// Add a state and the compiler and the reader both see the gap.JSX — Markup That Is Really JavaScript
JSX compiles to function calls. Knowing that explains every rule it has, including the ones that look arbitrary.
- ✓JSX compiles to function calls returning plain objects — every syntax rule follows from that
- ✓className and htmlFor exist because class and for are reserved JavaScript words
- ✓Braces accept expressions only; if, for and switch go above the return statement
- ✓{count && <Badge/>} renders a literal 0 when count is 0 — compare explicitly instead
- ✓React escapes interpolated values by default; dangerouslySetInnerHTML is the deliberate exception
// You write
<button className="primary" onClick={handleRun}>Run</button>
// The compiler produces
jsx('button', { className: 'primary', onClick: handleRun, children: 'Run' })
// which evaluates to a plain object describing what to render:
{ type: 'button', props: { className: 'primary', ... } }
// Consequences that follow directly:
// - 'class' is a reserved word -> className
// - 'for' is a reserved word -> htmlFor
// - attributes are JS properties -> onClick, tabIndex, readOnly
// - a component returns ONE value -> one root element, or a Fragment
<> {/* Fragment — groups without a wrapper div */}
<Header />
<Main />
</>
// A capital letter means "component"; lowercase means "DOM element".
<button /> // the HTML button
<Button /> // your componentComponents and Props
A component is a function taking props and returning UI. Props are read-only inputs, and treating them as read-only is what keeps a React app predictable.
- ✓A component is a function from props to UI, and must be pure for the same props
- ✓Props are read-only — copy before sorting or mutating, or you corrupt the parent's data
- ✓children makes composition the default; prefer it to accumulating boolean flags
- ✓Split a component when it has its own state, its own reason to change, or a second usage
- ✓Defining a component inside another component remounts it every render, losing state and focus
function ProblemCard({ problem, locked = false, onOpen }) {
return (
<article onClick={() => onOpen(problem.slug)}>
<h3>{problem.title}</h3>
<Difficulty level={problem.difficulty} />
{locked && <ProBadge />}
</article>
)
}
<ProblemCard problem={p} onOpen={open} /> // locked defaults to false
<ProblemCard problem={p} locked onOpen={open} /> // shorthand for locked={true}
// Props are read-only. This is a bug, not a shortcut:
function Bad({ items }) {
items.sort() // mutates the parent's array
return <List items={items} />
}
function Good({ items }) {
const sorted = [...items].sort() // copy first
return <List items={sorted} />
}Lists and Keys
A key tells React which item is which between renders. Using the array index is the single most common source of "the wrong row updated" bugs.
- ✓Keys let React match elements between renders; they must be unique among siblings, not globally
- ✓An index key attaches state to a position, so deleting or reordering moves state onto the wrong item
- ✓Index keys are safe only for a static list that never reorders and whose rows hold no state
- ✓A random key regenerated each render destroys and rebuilds the whole list
- ✓Changing a component's key deliberately resets all of its internal state
<ul>
{problems.map(problem => (
<ProblemRow key={problem.id} problem={problem} />
))}
</ul>
// The key goes on the outermost element of the map, not inside it
{rows.map(r => <Fragment key={r.id}><dt>{r.k}</dt><dd>{r.v}</dd></Fragment>)}
// Keys must be unique among SIBLINGS, not globally.
// Two different lists can both use key={1}.
// Filtering and sorting are just JavaScript
{problems
.filter(p => p.difficulty === level)
.sort((a, b) => a.order - b.order)
.map(p => <ProblemRow key={p.id} problem={p} />)}
// Empty state, always
{problems.length === 0
? <EmptyState />
: problems.map(...)}Handling Events
Pass a function, do not call one. Most early event bugs are one pair of parentheses in the wrong place.
- ✓Event props take a function reference; adding parentheses calls it during render
- ✓Wrap in an arrow function to pass arguments — inline handlers are rarely a real performance problem
- ✓React attaches one listener per event type at the root and hands you a normalised SyntheticEvent
- ✓A document listener you add yourself fires before React handlers — use mousedown for click-outside
- ✓A clickable div is unreachable by keyboard; use a button, and disable it while a submit is in flight
<button onClick={handleRun}>Run</button> // correct — a reference
<button onClick={handleRun()}>Run</button> // WRONG — calls it now
// Passing an argument needs a wrapper
<button onClick={() => onOpen(problem.slug)}>Open</button>
// Inline is fine. "It creates a new function every render" is true
// and almost never the reason a page is slow — measure before
// contorting the code around it.
// The event object
function handleSubmit(e) {
e.preventDefault() // stop the browser navigating
e.stopPropagation() // stop it reaching parent handlers
e.currentTarget.value
}
// Common handlers
onChange onInput onSubmit onKeyDown onFocus onBlur
onMouseEnter onPointerDown onScrollRendering and Reconciliation — What Happens on a State Change
A render is React calling your function, not React touching the DOM. Separating those two ideas explains re-render behaviour, keys, and most performance advice.
- ✓Rendering calls your function to produce a description; committing applies the minimal DOM changes
- ✓A re-render is cheap unless it does expensive work or produces actual DOM changes
- ✓A component re-renders when its own state changes, its parent re-renders, or a consumed context changes
- ✓Mutating an object does not trigger anything — React compares with Object.is, so pass a new value
- ✓State belongs to a position and type in the tree, so changing the type or the key resets it
// 1. TRIGGER — initial mount, or a state update in this component
// or in one of its ancestors
// 2. RENDER — React calls your function. It must be PURE: no DOM
// writes, no fetches, no mutating outside variables.
// The output is a description, not DOM.
// 3. COMMIT — React diffs against the previous description and
// applies only what changed to the real DOM.
// 4. EFFECTS — after the browser paints, effects run.
// So this component re-renders (function called) but produces the
// same output, and the commit phase does nothing at all:
function Header() { return <h1>AiCanCode</h1> }
// "It re-rendered" is not automatically a problem. The question is
// whether the render did expensive work, or produced DOM changes.Project Structure and Conventions
Group by feature, not by file type. The folder layout that feels tidy at ten components is the one that becomes unnavigable at two hundred.
- ✓Group by feature rather than by file type, so one change touches one folder
- ✓Colocate tests, styles and types beside the component they belong to
- ✓A shared component that imports from a feature is not shared — move it into that feature
- ✓Hooks must start with "use" for the lint rules to check them; components use PascalCase
- ✓Path aliases keep imports stable across moves; avoid a global constants file nothing owns
src/
features/
problems/
ProblemList.jsx
ProblemCard.jsx
ProblemCard.test.jsx // colocated
useProblems.js // the feature's own hook
api.js // the feature's own requests
types.ts
auth/
LoginForm.jsx
useAuth.js
AuthProvider.jsx
components/ // shared, generic, no feature knowledge
Button.jsx
Modal.jsx
Spinner.jsx
lib/ // shared non-UI helpers
apiClient.js
formatDate.js
App.jsx
// The test: to delete "problems", delete one folder.
// With type-based folders you would hunt through four.
// A shared component must not import from features/.
// If it needs to, it is not shared — it belongs to that feature.