Skip to Content

React

Targeting React 19

Syntax

Component
interface Props { name: string age?: number } function Greeting({ name, age = 0 }: Props) { return <p>Hi {name}, age {age}</p> }
Children
interface Props { children: React.ReactNode } function Card({ children }: Props) { return <div className="card">{children}</div> } // usage <Card><p>Hello</p></Card>
Fragment
// return multiple elements without a wrapper function List() { return ( <> <li>One</li> <li>Two</li> </> ) }
Conditional Render
function Status({ ok }: { ok: boolean }) { return ( <div> {ok ? <p>Success</p> : <p>Error</p>} {ok && <span>All good</span>} </div> ) }
List Render
function UserList({ users }: { users: User[] }) { return ( <ul> {users.map((u) => ( <li key={u.id}>{u.name}</li> ))} </ul> ) }
Event Handler
function Button() { const handleClick = (e: React.MouseEvent) => { e.preventDefault() console.log('clicked') } return <button onClick={handleClick}>Go</button> }
Style & Class
// inline style — object with camelCase keys <div style={{ color: 'red', fontSize: 14 }} /> // className — not class <div className="card active" /> // conditional classes <div className={`tab ${active ? 'selected' : ''}`} />
Spread Props
interface BtnProps extends React.ButtonHTMLAttributes<HTMLButtonElement> { variant?: 'primary' | 'ghost' } function Btn({ variant = 'primary', ...rest }: BtnProps) { return <button className={variant} {...rest} /> } // passes onClick, disabled, etc. through

Beginner Patterns

useState
const [count, setCount] = useState(0) setCount(5) // set directly setCount((prev) => prev + 1) // updater function // typed state const [user, setUser] = useState<User | null>(null)
useEffect
// run on mount useEffect(() => { fetchData() }, []) // empty deps = mount only // run when dep changes useEffect(() => { fetchUser(id) }, [id]) // re-runs when id changes // cleanup useEffect(() => { const sub = subscribe() return () => sub.unsubscribe() // cleanup on unmount }, [])
useRef
// DOM reference const inputRef = useRef<HTMLInputElement>(null) inputRef.current?.focus() // mutable value that doesn't trigger re-render const renderCount = useRef(0) renderCount.current++ // in JSX <input ref={inputRef} />
Form Input
const [name, setName] = useState('') <input value={name} onChange={(e) => setName(e.target.value)} placeholder="Name" />
Lifting State
// parent owns the state, passes down function Parent() { const [value, setValue] = useState('') return ( <> <Input value={value} onChange={setValue} /> <Display text={value} /> </> ) } function Input({ value, onChange }: { value: string onChange: (v: string) => void }) { return <input value={value} onChange={(e) => onChange(e.target.value)} /> }
Fetch on Mount
const [data, setData] = useState<Post[]>([]) const [loading, setLoading] = useState(true) useEffect(() => { fetch('/api/posts') .then((res) => res.json()) .then((posts) => setData(posts)) .finally(() => setLoading(false)) }, []) if (loading) return <p>Loading...</p>

Intermediate Patterns

useCallback
// memoize a function so it keeps the same reference const handleDelete = useCallback((id: number) => { setItems((prev) => prev.filter((i) => i.id !== id)) }, []) // without useCallback, a new function is created every render // pass to memoized children to prevent unnecessary re-renders
useMemo
// memoize an expensive computation const sorted = useMemo( () => items.toSorted((a, b) => a.price - b.price), [items] // recompute only when items changes ) // don't overuse — only for genuinely expensive work
Context
interface ThemeCtx { theme: string; toggle: () => void } const ThemeContext = createContext<ThemeCtx | null>(null) function ThemeProvider({ children }: { children: React.ReactNode }) { const [theme, setTheme] = useState('dark') const toggle = () => setTheme((t) => t === 'dark' ? 'light' : 'dark') return ( <ThemeContext value={{ theme, toggle }}> {children} </ThemeContext> ) } // consume const ctx = useContext(ThemeContext)
useReducer
type Action = | { type: 'inc' } | { type: 'dec' } | { type: 'set'; value: number } function reducer(state: number, action: Action) { switch (action.type) { case 'inc': return state + 1 case 'dec': return state - 1 case 'set': return action.value } } const [count, dispatch] = useReducer(reducer, 0) dispatch({ type: 'inc' })
Custom Hook
function useLocalStorage<T>(key: string, initial: T) { const [value, setValue] = useState<T>(() => { const stored = localStorage.getItem(key) return stored ? JSON.parse(stored) : initial }) useEffect(() => { localStorage.setItem(key, JSON.stringify(value)) }, [key, value]) return [value, setValue] as const } const [theme, setTheme] = useLocalStorage('theme', 'dark')
Portal
import { createPortal } from 'react-dom' function Modal({ children }: { children: React.ReactNode }) { return createPortal( <div className="modal-overlay"> <div className="modal">{children}</div> </div>, document.body // render outside the component tree ) }

Advanced Patterns

forwardRef
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> { label: string } const Input = forwardRef<HTMLInputElement, InputProps>( ({ label, ...props }, ref) => ( <label> {label} <input ref={ref} {...props} /> </label> ) ) // parent can ref the inner <input> const ref = useRef<HTMLInputElement>(null) <Input ref={ref} label="Name" />
Generic Component
interface ListProps<T> { items: T[] renderItem: (item: T) => React.ReactNode } function List<T>({ items, renderItem }: ListProps<T>) { return <ul>{items.map(renderItem)}</ul> } <List items={users} renderItem={(u) => <li key={u.id}>{u.name}</li>} />
Compound Component
function Tabs({ children }: { children: React.ReactNode }) { const [active, setActive] = useState(0) return ( <TabsContext value={{ active, setActive }}> {children} </TabsContext> ) } Tabs.Tab = function Tab({ index, children }: { index: number; children: React.ReactNode }) { const { active, setActive } = useContext(TabsContext)! return ( <button className={active === index ? 'active' : ''} onClick={() => setActive(index)} >{children}</button> ) }
Suspense + Lazy
import { lazy, Suspense } from 'react' const Settings = lazy(() => import('./Settings')) function App() { return ( <Suspense fallback={<p>Loading...</p>}> <Settings /> </Suspense> ) } // Settings chunk loads only when rendered
Error Boundary
import { ErrorBoundary } from 'react-error-boundary' function Fallback({ error }: { error: Error }) { return <p>Something broke: {error.message}</p> } <ErrorBoundary FallbackComponent={Fallback}> <RiskyComponent /> </ErrorBoundary>
useId
// generate unique IDs for accessibility — SSR safe function FormField({ label }: { label: string }) { const id = useId() return ( <> <label htmlFor={id}>{label}</label> <input id={id} /> </> ) } // each instance gets a unique id like ':r1:', ':r2:'
Last updated on