Quick start
Installing React
npm install react react-dom
TypeScript projects also need @types/react and @types/react-dom.
Create a root
import { createRoot } from 'react-dom/client'
createRoot(document.getElementById('root')).render(<App />)
The automatic JSX runtime (v17+) means no import React. See: createRoot
StrictMode
<StrictMode><App /></StrictMode>
Double-invokes renders and effects in development to surface impure logic; no effect in production. See: StrictMode
Components
Function components
function Hello({ name }) {
return (
<h1 className="greeting">
Hello {name}
</h1>
)
}
Capitalised functions that return JSX. See: Your first component
JSX and conditionals
<label htmlFor="name">Name</label>
<p style={{ color: 'red' }}>{2 + 2}</p>
<>
{isLoggedIn ? (
<AdminPanel />
) : (
<LoginButton />
)}
{unread > 0 && (
<Badge count={unread} />
)}
</>
{} embeds expressions, attributes are camelCase and <>…</> is a fragment; null and booleans render nothing. See: Writing markup with JSX
Props
function Avatar({ src, alt, size = 64 }) {
return (
<img
src={src}
alt={alt}
width={size}
height={size}
/>
)
}
<Avatar
src="/me.png"
alt="Me"
size={128}
/>
Defaults come from parameters (v19 removed defaultProps); spread extras with {...props}. See: Passing props
Lists and keys
{items.map(item => (
<li key={item.id}>{item.name}</li>
))}
Keys must be stable and unique among siblings. See: Rendering lists
Events
<button onClick={handleClick}>
Save
</button>
<button onClick={() => remove(id)}>
Delete
</button>
<form
onSubmit={e => {
e.preventDefault()
save()
}}
>
…
</form>
Pass the handler, not a call, and wrap arguments in an arrow; call e.preventDefault() to stop browser defaults. See: Responding to events
State
useState
const [count, setCount] = useState(0)
setCount(count + 1)
setCount(c => c + 1) // when the next value depends on the previous
setUser({ ...user, name: 'Ana' })
setItems(items => [...items, item])
setItems(items => items.filter(i => i.id !== id))
State is a per-render snapshot and is immutable — replace it, never mutate in place. See: useState
Controlled inputs
const [name, setName] = useState('')
<input value={name} onChange={e => setName(e.target.value)} />
React owns the value, so the input always reflects state. See: input
Lifting state up
function Parent() {
const [query, setQuery] = useState('')
return <Search value={query} onChange={setQuery} />
}
Move shared state to the closest common parent and pass value + setter down. See: Sharing state
useReducer
function reducer(state, action) {
if (action.type === 'increment') {
return { count: state.count + 1 }
}
return state
}
const [state, dispatch] = useReducer(reducer, { count: 0 })
dispatch({ type: 'increment' })
Prefer a reducer when updates are complex or depend on several values. See: useReducer
Actions (v19)
const [error, submitAction, isPending] =
useActionState(async (prev, formData) => {
return (await updateName(formData.get('name'))) ?? null
}, null)
<form action={submitAction}>
<input name="name" />
<button disabled={isPending}>Update</button>
{error && <p>{error}</p>}
</form>
Actions are async functions in action props; useOptimistic shows an immediate value while the request runs. See: useActionState
Effects
useEffect
useEffect(() => {
const connection = createConnection(roomId)
connection.connect()
return () => connection.disconnect() // cleanup
}, [roomId]) // re-runs when roomId changes
useEffect(() => {
const id = setInterval(tick, 1000)
return () => clearInterval(id)
}, []) // once on mount; twice in StrictMode dev
Synchronises a component with an external system and cleans up timers, subscriptions and listeners. See: useEffect
Fetching data
useEffect(() => {
const controller = new AbortController()
fetch(`/api/users/${id}`, { signal: controller.signal })
.then(r => {
if (!r.ok) throw new Error(`HTTP ${r.status}`)
return r.json()
})
.then(setUser)
.catch(err => { if (err.name !== 'AbortError') setError(err) })
return () => controller.abort()
}, [id])
Abort in the cleanup to drop stale responses, ignoring the expected AbortError. See: Fetching data
useEffectEvent (v19.2+)
const onConnected = useEffectEvent(() => {
showNotification('Connected!', theme)
})
useEffect(() => {
const connection = createConnection(roomId)
connection.on('connected', onConnected)
connection.connect()
return () => connection.disconnect()
}, [roomId])
Reads the latest props and state without re-running the effect, so it stays out of the deps. See: useEffectEvent
You might not need an effect
// prefer: derive during render
const fullName = first + ' ' + last
// avoid: derived state in an effect
useEffect(() => {
setFullName(first + ' ' + last)
})
Don't use effects to transform data or to react to events. See: You might not need an effect
Refs
useRef
const inputRef = useRef(null)
useEffect(() => inputRef.current.focus(), [])
return <input ref={inputRef} />
A mutable .current box that persists without re-rendering. See: useRef
Ref as a prop (v19)
function Input({ ref, ...props }) {
return <input ref={ref} {...props} />
}
v19 passes ref as a normal prop; v18 uses forwardRef, no longer necessary in v19 and slated for deprecation. See: forwardRef
useImperativeHandle
useImperativeHandle(ref, () => ({
play: () => videoRef.current.play()
}), [])
Exposes a custom imperative API instead of the DOM node; rarely needed. See: useImperativeHandle
Portals and inner HTML
import { createPortal } from 'react-dom'
createPortal(<Modal />, document.getElementById('modal-root'))
<div dangerouslySetInnerHTML={{ __html: trustedHtml }} />
A portal renders elsewhere in the DOM while keeping the React tree; dangerouslySetInnerHTML injects raw HTML, so never pass untrusted input. See: createPortal
Context
createContext and useContext
const ThemeContext = createContext(null)
<ThemeContext value="dark"><Toolbar /></ThemeContext>
function Toolbar() {
const theme = useContext(ThemeContext)
return <button className={theme}>Hi</button>
}
<ThemeContext value="dark">…</ThemeContext> // v19
<ThemeContext.Provider value="dark">…</ThemeContext.Provider> // v18
function useTheme() {
const value = useContext(ThemeContext)
if (value === null) throw new Error('useTheme needs a provider')
return value
}
Context passes data down without prop drilling and v19 renders <Context> itself as the provider; wrapping useContext names the value and fails loudly outside a provider. See: useContext · createContext
Custom hooks
Call hooks at the top level of a component or hook — never in loops, conditions or nested functions. See: Rules of hooks
useOnlineStatus
function useOnlineStatus() {
const [isOnline, setOnline] = useState(true)
useEffect(() => {
const update = () => setOnline(navigator.onLine)
update()
window.addEventListener('online', update)
window.addEventListener('offline', update)
return () => {
window.removeEventListener('online', update)
window.removeEventListener('offline', update)
}
}, [])
return isOnline
}
A custom hook is just a function that calls other hooks. See: Custom hooks
useId and useDebugValue
const id = useId()
<label htmlFor={id}>Name</label>
<input id={id} />
useId gives SSR-safe ids for accessibility; useDebugValue(label) labels a hook in DevTools. See: useId
Performance
memo, useMemo and useCallback
const Row = memo(function Row({ item }) {
return <li>{item.name}</li>
})
const visible = useMemo(() => filter(items, query), [items, query])
const onSelect = useCallback(id => setSelected(id), [])
Cache components and values only when measurement shows a need — the React Compiler, once configured in your build, auto-memoises and removes most manual calls. See: memo
useTransition and useDeferredValue
const [isPending, startTransition] = useTransition()
startTransition(() => setQuery(input))
const deferredQuery = useDeferredValue(query)
useTransition marks an update non-blocking so urgent ones (typing) stay responsive; useDeferredValue renders a stale value first, then catches up. See: useTransition · useDeferredValue
useSyncExternalStore
const isOnline = useSyncExternalStore(
subscribe,
() => navigator.onLine,
() => true
)
Subscribes safely to an external store in concurrent rendering; the third argument is the server snapshot. See: useSyncExternalStore
Suspense and lazy
lazy and Suspense
const Chart = lazy(() => import('./Chart'))
<Suspense fallback={<Spinner />}><Chart /></Suspense>
Code-splits a component; the fallback shows while it loads. See: Suspense
use(promise) (v19)
function Comments({ commentsPromise }) {
const comments = use(commentsPromise)
return comments.map(c => <p key={c.id}>{c.text}</p>)
}
Reads a promise or context during render and suspends; unlike hooks it may be conditional. See: use
Rendering and SSR
Client rendering
// fresh DOM
createRoot(document.getElementById('root'))
.render(<App />)
// server HTML
hydrateRoot(
document.getElementById('root'),
<App />
)
Hydration markup must match the client render. See: Client APIs
Server rendering
import { renderToString } from 'react-dom/server'
const html = renderToString(<App />) // synchronous, blocks
renderToPipeableStream (Node) and renderToReadableStream (web) stream with Suspense; v19 adds prerender. See: Server APIs
Error handling
Error boundaries
class ErrorBoundary extends Component {
state = { error: null }
static getDerivedStateFromError(error) {
return { error }
}
componentDidCatch(error, info) {
logError(error, info)
}
render() {
if (this.state.error) {
return <h1>Something went wrong.</h1>
}
return this.props.children
}
}
Still the only built-in way to catch render errors, and still a class; react-error-boundary wraps it with a hooks-friendly API. See: Error boundaries
Legacy APIs
Rendering and lifecycle
| Legacy | Modern |
|---|---|
ReactDOM.render(el, node) | createRoot(node).render(el) |
ReactDOM.hydrate(el, node) | hydrateRoot(node, el) |
componentDidMount / componentDidUpdate | useEffect |
componentWillUnmount | effect cleanup |
| Class components | function components |
Components and props
| Legacy | Modern |
|---|---|
defaultProps (function components) | default parameter values |
propTypes | TypeScript |
React.createElement | JSX |
Refs and context
| Legacy | Modern |
|---|---|
String refs ref="input" | ref callback or useRef |
forwardRef (v18) | ref as a prop (v19) |
Legacy context (getChildContext) | createContext |
<Context.Provider value> (v18) | <Context value> (v19) |
Further reading
See: Legacy APIs. Classes still work and are required only for error boundaries; see the React v16 cheatsheet for the class-era API.
Also see
- React documentation (react.dev)
- React API reference (react.dev)
- TypeScript cheatsheet — typed props and hooks
- React v16 cheatsheet — class components and legacy APIs
- React v0.14 cheatsheet —
React.createClassera
0 Comments for this cheatsheet. Write yours!