Since my graduate years, I’ve always asked professors if I’m able to carry a cheatsheet to exams, for both Math and CS courses. I can never really remember syntax or formulas — luckily, I have a fairly good understanding of concepts and what steps to take to get to solutions, so I’ve never bothered with memorizing. I decided to publish my personal cheatsheets on some of my top-used coding patterns and basic defaults that I usually have in my projects. Starting with TypeScript for today. If I ever get stuck on a task, I just open the link to my blog to get the exact reference I need. More patterns will be added over time.
TypeScript Types
Primitive types
string // text
number // integers and floats
boolean // true / false
null // explicitly empty
undefined // not yet assigned
Object type
The type keyword defines the shape of an object. Optional fields use ?. interface is interchangeable for objects — type is more flexible since it also supports unions and intersections. Pick one and stick with it.
type Person = {
name: string
age: number
active?: boolean // ? = optional field (can be undefined)
}
When: any structured data (form values, todo items, users).
Pitfall: don’t use interface and type interchangeably without reason — pick type and stick with it for component props/state.
Union type
type Status = 'idle' | 'loading' | 'error' | 'success'
type Theme = 'light' | 'dark'
type Color = 'red' | 'green' | 'yellow'
When: a value can only be one of a fixed set of strings.
Pitfall: don’t use plain string when you know the valid values — you lose autocomplete and type safety.
Array type
string[] // array of strings
number[] // array of numbers
Person[] // array of Person objects
When: list state (useState<Person[]>([])).
Pitfall: Person[] ≠ Person — passing a single item where an array is expected causes a type error.
Partial
Partial<Person> // makes every field optional
When: error state objects — you only set errors for fields that fail.
const errors: Partial<Person> = {}
errors.name = "Required" // valid — age is not required here
Pitfall: don’t use Partial for general state — it hides missing required fields.
Record
Record<string, number> // object with string keys and number values
Record<Color, number> // object with Color union keys and number values
const DURATIONS: Record<Color, number> = {
red: 3000,
green: 3000,
yellow: 1000,
}
When: lookup tables, duration maps, label maps. Pitfall: if keys are a union type, all union members must be present or TypeScript errors.
Tuple (as const)
const SEQUENCE = ['green', 'yellow', 'red'] as const
type Color = typeof SEQUENCE[number] // → 'green' | 'yellow' | 'red'
When: derive a union type from a literal array.
Pitfall: without as const, TypeScript infers string[] not 'green'[].
React.ReactNode
type Props = { children: React.ReactNode }
When: a component accepts any JSX as children (wrappers, providers, modals, cards).
Pitfall: don’t use JSX.Element — it’s narrower and rejects strings, arrays, null.
Generics
useState<Person[]>([])
useState<string | null>(null)
useRef<number | null>(null)
function useDebounce<T>(value: T, delay: number): T
When: the type depends on how the function/hook is called.
Pitfall: omitting the generic lets TypeScript infer — usually fine for useState, required for complex hooks.
Non-null assertion (!)
createContext<AuthCtx>(null!)
clearInterval(intervalRef.current!)
When: you know a value won’t be null at runtime but TypeScript can’t prove it. Pitfall: if you’re wrong, runtime crash. Use sparingly — only when you’re certain.
Nullish coalescing (??)
job.url ?? '#' // use '#' if job.url is null or undefined
item ?? defaultValue
When: fallback for null/undefined only.
Pitfall: ?? ≠ || — || also catches 0, '', false. Use ?? when 0 or '' are valid values.
Event Handlers
React’s event types are generic over the DOM element they originate from. Always type the element, not just the event category.
Input change
React.ChangeEvent<HTMLInputElement> // <input>, <textarea>
React.ChangeEvent<HTMLSelectElement> // <select>
Properties:
e.target.value— current field value (always string)e.target.name— thenameattribute of the inpute.target.checked— for checkboxes only (boolean)
function handleChange(e: React.ChangeEvent<HTMLInputElement>): void {
console.log(e.target.name) // "email"
console.log(e.target.value) // "user@..."
}
Pitfall: e.target.value is always string even for type="number". Convert with +e.target.value or Number(e.target.value).
Form submit
React.FormEvent<HTMLFormElement>
Always call e.preventDefault() first — stops page reload.
function handleSubmit(e: React.FormEvent<HTMLFormElement>): void {
e.preventDefault()
}
Button click
React.MouseEvent<HTMLButtonElement>
Often not needed — onClick={() => doSomething()} infers the type automatically.
function handleClick(e: React.MouseEvent<HTMLButtonElement>): void {
console.log("clicked")
}
Keyboard (document-level)
// Not a React type — browser native
function handler(e: KeyboardEvent) {
if (e.key === 'Escape') onClose()
}
document.addEventListener('keydown', handler)
When: global keyboard shortcuts (Escape to close modal).
Pitfall: must add inside useEffect and remove in cleanup — or the listener stacks up on every render.
useState Patterns
The generic type parameter <T> tells TypeScript what the state holds. Always match the initial value’s type.
Single value
const [count, setCount] = useState(0)
const [name, setName] = useState('')
const [open, setOpen] = useState(false)
const [selected, setSelected] = useState<number | null>(null)
Object (form fields)
const [form, setForm] = useState<FormValues>({ name: '', email: '', role: '' })
const [user, setUser] = useState<Person>({ name: '', age: 0 })
Array (list of items)
const [items, setItems] = useState<Person[]>([])
Functional update (when new state depends on old)
setCount(prev => prev + 1) // safe — always uses latest value
setItems(prev => [...prev, newItem]) // safe — always uses latest array
When: inside setInterval, setTimeout, or event handlers that close over stale state.
Pitfall: setCount(count + 1) inside an interval uses stale count — use functional form instead.
Array State Patterns
React state is immutable — never push or splice arrays directly. Always return a new array via prev => to avoid stale state.
Append
setItems(prev => [...prev, newItem])
Delete by index
setItems(prev => prev.filter((_, i) => i !== targetIndex))
Toggle a field on one item
Spread ...item copies all existing fields; only the target field is overwritten.
setItems(prev =>
prev.map((item, i) =>
i === targetIndex ? { ...item, active: !item.active } : item
)
)
Update a field on one item
setItems(prev =>
prev.map((item, i) =>
i === targetIndex ? { ...item, name: 'new name' } : item
)
)
Pitfall: never mutate state directly — items[0].active = true won’t trigger re-render. Always return a new array/object.
Spread + Computed Key
The computed property key [fieldName] lets a single onChange handler update any field dynamically — no need for separate setEmail, setPassword, etc.
setForm(prev => ({ ...prev, [e.target.name]: e.target.value }))
// e.g. name="email" → { ...prev, email: "value" }
...prevcopies all existing fields[e.target.name]uses the variable’s value as the key namee.target.valueis the new value
Pitfall: the name attribute on each <input> must match the field name in your type exactly.
useEffect Patterns
Run once on mount
useEffect(() => {
fetchData()
}, []) // empty array = run once
Run when a value changes
useEffect(() => {
console.log('query changed:', query)
}, [query]) // runs on mount + every time query changes
Cleanup (timers, listeners)
Returning a function registers a cleanup — runs before the next effect fires and on unmount.
useEffect(() => {
const id = setInterval(tick, 1000)
return () => clearInterval(id)
}, [dep])
Dependency array rules
| Array | When it runs |
|---|---|
[] | Once on mount |
[a, b] | Mount + whenever a or b changes |
| omitted | Every render (almost never what you want) |
Pitfall: omitting deps that the effect uses causes stale closures — ESLint’s exhaustive-deps rule catches this.
useRef
const intervalRef = useRef<number | null>(null)
const inputRef = useRef<HTMLInputElement>(null)
.currentholds the value- Changing
.currentdoes not trigger re-render - Survives re-renders without resetting
When:
- Storing interval/timeout IDs
- Accessing a DOM element directly (
inputRef.current?.focus()) - Storing previous values
intervalRef.current = setInterval(tick, 1000)
clearInterval(intervalRef.current!)
Pitfall: don’t use useRef for values that should trigger UI updates — use useState for that.
useReducer
type State = { count: number; history: number[] }
type Action = { type: 'increment' } | { type: 'undo' }
function reducer(state: State, action: Action): State {
switch (action.type) {
case 'increment': return { ...state, count: state.count + 1 }
case 'undo': return { ...state, count: state.history[0] }
}
}
const [state, dispatch] = useReducer(reducer, { count: 0, history: [] })
dispatch({ type: 'increment' })
When: multiple state values change together, or state transitions follow clear rules (undo, form steps, multi-phase UI).
Pitfall: overkill for simple state — prefer useState unless you have 3+ related values or complex transitions. Reducer must be pure — no side effects inside.
Context API
// 1. Create
const MyContext = createContext<CtxType>(defaultValue)
// 2. Provide (wrap the tree)
<MyContext.Provider value={...}>
{children}
</MyContext.Provider>
// 3. Consume (anywhere below the provider)
const value = useContext(MyContext)
When: state needed by many components at different nesting levels (theme, auth, language). Pitfall: every consumer re-renders when context value changes — split contexts if different parts of state change at different rates.
JSX Patterns
Conditional render
{isOpen && <Modal />} // render if true
{error ? <p>{error}</p> : null} // ternary
{loading ? <Spinner /> : <Content />} // ternary with two branches
Pitfall: {count && <p>{count}</p>} — if count is 0, renders 0 (falsy but not false). Use {count > 0 && ...} instead.
List render
{items.map((item, index) => (
<div key={item.id}>{item.name}</div>
))}
Key rules:
- Must be unique among siblings
- Stable — don’t use
Math.random() - Use
item.idwhen available;indexonly if list never reorders or deletes
React uses key like a hashmap internally: stable key = update existing node; changed key = destroy + recreate node (loses internal state).
Controlled input
<input
name="email"
value={form.email}
onChange={e => setForm(prev => ({ ...prev, email: e.target.value }))}
/>
value is driven by state; onChange updates state. React is the single source of truth.
Controlled select
<select value={role} onChange={e => setRole(e.target.value)}>
<option value="eng">Engineer</option>
<option value="des">Designer</option>
</select>
Controlled checkbox
<input
type="checkbox"
checked={completed}
onChange={e => setCompleted(e.target.checked)} // .checked not .value
/>
Inline style
<p style={{ color: 'red', fontSize: 16, textDecoration: 'line-through' }}>
- camelCase properties:
backgroundColor,borderRadius,textDecoration - Numbers default to
px:fontSize: 16=font-size: 16px - Strings for non-px:
fontSize: '1rem',width: '100%'
onClick — calling vs referencing
onClick={handleReset} // ✓ calls handleReset on click
onClick={() => handleReset()} // ✓ also fine (needed when passing args)
onClick={() => handleReset} // ✗ returns the function, never calls it
onClick={handleReset()} // ✗ calls immediately on render, not on click
Rule: if you need to pass arguments use () => fn(arg). If no arguments, just fn.
Fragment
<>
<Child1 />
<Child2 />
</>
Use when you need multiple root elements without adding a real DOM node.
children prop
function Card({ children }: { children: React.ReactNode }) {
return <div className="card">{children}</div>
}
<Card><p>Any JSX here</p></Card>
Common Utility Patterns
padStart — format numbers
String(5).padStart(2, '0') // → "05"
// Use for: timers, clocks, any zero-padded display
Array.from — generate arrays
Array.from({ length: 5 }, (_, i) => i + 1) // → [1, 2, 3, 4, 5]
// Use for: star ratings, grids, pagination ranges
Cycle index
setIndex(i => (i + 1) % arr.length) // wraps back to 0 after last item
// Use for: traffic light, image carousel, round-robin
String to number
+e.target.value // unary plus — fast
Number(e.target.value) // explicit — same result
// Use when input type="number" but value is always string
Object.keys check
Object.keys(errors).length > 0 // true = object has at least one key
// Use for: checking if validation errors exist before submitting
Promise.all — parallel fetch
const results = await Promise.all(
ids.map(id => fetch(url + id).then(r => r.json()))
)
// Use for: fetching multiple items at once instead of sequentially
stopPropagation
e.stopPropagation()
// Stops event bubbling to parent elements
// Use for: clicking inside a modal without closing the overlay behind it
Quick Hook Reference
| Hook | Use for | Key gotcha |
|---|---|---|
useState | Any reactive value | Use functional update when new state depends on old |
useEffect | Side effects, fetch, timers, listeners | Always return cleanup for timers/listeners |
useRef | Mutable value without re-render, DOM access | .current change doesn’t trigger re-render |
useReducer | Complex state with multiple transitions | Reducer must be pure — no side effects inside |
useContext | Share state without prop drilling | All consumers re-render on context change |
| Custom hook | Extract reusable stateful logic | Must start with use, can call other hooks |
Lessons from Practice
Shadow variable — local const vs state
// Bad — local errors shadows the state variable, confusing to read
const [errors, setErrors] = useState<Partial<FormValues>>({})
function handleSubmit() {
const errors: Partial<FormValues> = {} // ← shadows outer errors
}
// Good — rename the local validation object
const validationErrors: Partial<FormValues> = {}
if (Object.keys(validationErrors).length > 0) {
setErrors(validationErrors)
}
Derive state, don’t duplicate it
// Bad — two states tracking the same thing
const [toggle, setToggle] = useState(false)
const [text, setText] = useState('Start') // redundant
// Good — derive text from toggle
<button>{toggle ? 'Stop Recording' : 'Start Recording'}</button>
Rule: if a value can be calculated from existing state, don’t store it separately.
setInterval — increment vs interval are independent
setInterval(() => setElapsed(prev => prev + 10), 10)
// ↑ ↑
// how much to add how often (ms)
Smaller delay = fires more often = smoother display. The two numbers can differ — e.g. +100 every 10ms is valid. Always use prev => inside intervals; the plain variable (elapsed) is stale (frozen at mount value).
Timer display math
// elapsed = total milliseconds
const mm = String(Math.floor(elapsed / 60000)).padStart(2, '0')
const ss = String(Math.floor((elapsed % 60000) / 1000)).padStart(2, '0')
// elapsed = 75430ms → mm = "01", ss = "15"
// % 60000 removes whole minutes → divide by 1000 converts remainder ms → seconds
Pitfall: elapsed % 60000 gives milliseconds, not seconds — always divide by 1000.
useEffect — boolean flag pattern (start/stop)
useEffect(() => {
if (running) {
intervalRef.current = window.setInterval(...)
} else {
clearInterval(intervalRef.current!)
}
return () => clearInterval(intervalRef.current!) // safety net on unmount
}, [running]) // re-runs when running flips
[running] not [] — the effect must re-run when the flag changes or Start/Pause does nothing. The return () => is the unmount safety net, not the same as the else branch.
window.setInterval vs setInterval
window.setInterval(fn, 10) // explicitly browser — returns number (TypeScript happy)
setInterval(fn, 10) // may infer NodeJS.Timer in TS — use window. to be safe
Context — outside Provider = no data
<AuthProvider>
<Profile /> // ✓ useAuth() works
</AuthProvider>
<ProfileOutside /> // ✗ useAuth() returns null — runtime crash if destructured
Rule: wrap the Provider as high as possible (usually main.tsx) so the whole app is covered.
Accordion toggle — one open at a time
// state: number | null — null means all closed
setOpenIndex(prev => prev === index ? null : index)
// Already open → close (null)
// Different item → open it (implicitly closes the previous)
Hope these patterns are useful to anyone ramping up on React with TypeScript. I’ll keep adding to this as I encounter more patterns worth bookmarking.
Personal TypeScript Cheatsheet: React + Frontend Patterns
Practical TypeScript patterns I use daily in React projects — types, state, event handlers, JSX, hooks, and utilities. Quick reference for when syntax escapes me mid-task.