Concept lesson · explains an idea. Code is shown for reading, not run.
A quick reference for everything in this course (React 19.3). Each section links to the full lesson.
Project commands
npm create vite@latest my-app -- --template react # JavaScript (react-ts for TypeScript)
npm install # install dependencies
npm run dev # development server
npm run build # production build into dist
npm run preview # serve dist locally
npm install react-router # add a package
Creating a Project · package.json
Starting an app
import { createRoot } from 'react-dom/client'
createRoot(document.getElementById('root')).render(<App />)
JSX rules
| Rule | Example |
|---|---|
| One root element, or a Fragment | <>...</> |
| Close every tag | <img />, <br /> |
className, htmlFor, camelCase attributes | <label htmlFor="a" className="b" tabIndex={0}> |
{} takes expressions only | {price * 2}, {ok ? "Yes" : "No"} |
style takes an object | style={{ color: "red", fontSize: 16 }} |
| Comments | {/* note */} |
| Components start with a capital letter | <UserCard /> |
Components and props
function Card({ title, size = "md", children, onClose }) {
return <section className={size}>{title}{children}<button onClick={onClose}>x</button></section>
}
<Card title="Hi" onClose={() => {}}>content</Card>
<Card {...props} />
Props flow down; functions flow up; never change props. Props · children
Conditional rendering and lists
{cond && <A />} // number conditions: count > 0 && ...
{cond ? <A /> : <B />}
if (loading) return <Spinner /> // early return
{items.map((item) => <Row key={item.id} item={item} />)} // stable unique key
{items.length === 0 && <p>Nothing here</p>}
State
const [value, setValue] = useState(initial) // lazy: useState(() => compute())
setValue(next) // replace
setValue((prev) => prev + 1) // based on the previous value
| Update | Pattern |
|---|---|
| Object | setObj({ ...obj, field: x }) |
| Add to array | setList([...list, item]) |
| Remove | setList(list.filter((x) => x.id !== id)) |
| Replace one | setList(list.map((x) => (x.id === id ? { ...x, done: true } : x))) |
| Sort | [...list].sort(compare) |
Never mutate state. Do not store what you can calculate. useState · Mutation
Events
<button onClick={handleClick}> // pass the function, do not call it
<button onClick={() => remove(id)}> // wrap to pass arguments
<input value={text} onChange={(e) => setText(e.target.value)} />
<input type="checkbox" checked={on} onChange={(e) => setOn(e.target.checked)} />
<form onSubmit={(e) => { e.preventDefault(); /* ... */ }}>
Hooks at a glance
| Hook | Use for |
|---|---|
useState | Values that change and are shown |
useReducer | Many related updates in one function |
useEffect | Syncing with something outside React (not for calculations) |
useRef | DOM nodes, timer ids, values that do not re-render |
useMemo | Caching a costly calculation (measure first) |
useCallback | Stable function identity for memoized children |
useContext | Reading shared data without prop drilling |
useId | Unique ids for labels |
useTransition, useDeferredValue | Keeping the UI responsive |
useActionState, useFormStatus | Form actions (React 19) |
use | Reading a promise or context during render |
Rules: call hooks at the top level, only in components and custom hooks. Rules · Other Hooks
Effects
useEffect(() => {
const id = setInterval(tick, 1000)
return () => clearInterval(id) // cleanup
}, [dependencies]) // [] once, [a, b] when they change, none: every render
Fetch with an ignore flag or AbortController. Effects · Dependencies · Fetching
Context and custom hooks
const ThemeContext = createContext(null)
<ThemeContext value={theme}>...</ThemeContext> // React 19 (ThemeContext.Provider before)
const theme = useContext(ThemeContext)
function useToggle(initial = false) { // name starts with "use"
const [on, setOn] = useState(initial)
return [on, () => setOn((v) => !v)]
}
Styling
import './Card.css' // global CSS file
import styles from './Card.module.css' // CSS Module: styles.title
<div className={['card', active && 'card--on'].filter(Boolean).join(' ')} />
<div style={{ width: percent + '%' }} /> // computed values
Data
const res = await fetch(url, { signal })
if (!res.ok) throw new Error('HTTP ' + res.status) // fetch does not reject on 404/500
const data = await res.json()
Track status: loading, error, empty, success. Never put secrets in client code. Fetch API · Secret Keys
Routing (React Router 8)
import { BrowserRouter, Routes, Route, Link, NavLink, Outlet, useParams, useNavigate, useSearchParams } from 'react-router'
<BrowserRouter>
<Routes>
<Route element={<Layout />}> // layout with <Outlet />
<Route index element={<Home />} />
<Route path="products/:id" element={<Product />} />
<Route path="*" element={<NotFound />} />
</Route>
</Routes>
</BrowserRouter>
Code splitting and errors
const Reports = lazy(() => import('./Reports.jsx'))
<Suspense fallback={<p>Loading...</p>}><Reports /></Suspense>
// Error boundaries are class components (or react-error-boundary)
Lazy Loading · Error Boundaries
TypeScript
type Props = { name: string; count?: number; children?: ReactNode }
const [user, setUser] = useState<User | null>(null)
const ref = useRef<HTMLInputElement>(null)
function onChange(e: ChangeEvent<HTMLInputElement>) {}
Testing
render(<Counter />)
await user.click(screen.getByRole('button', { name: 'Add one' }))
expect(screen.getByText('Count: 1')).toBeInTheDocument()
The golden rules
- UI is a function of state: describe it, do not mutate the DOM.
- State is read-only: set new values.
- Data flows down, events flow up.
- Keep state minimal and derive the rest.
- Effects are for syncing with outside systems, with cleanup.
- Keys are stable ids.
- Measure before you optimize; make it accessible from the start.