functionWrapper({ children }){return<div className="wrapper">{children}</div>;}
Example
functionCard({ children, title }){return(<section className="card"><h2>{title}</h2><div className="card-body">{children}</div></section>);}// Usage<Card title="Profile"><p>This content is passed as children.</p></Card>
Output
Renders a card wrapper around the paragraph.
Note children can be any renderable content: text, elements, arrays, or even functions (for render props).
children propwrapper componentpass childrennested contentslot pattern
Default Prop Values
Syntax
functionComponent({ propA = defaultValue }){...}
Example
functionBadge({ label ="Member", color ="gray"}){return(<span style={{ backgroundColor: color }}>{label}</span>);}// Uses defaults<Badge/>// Overrides<Badge label="Admin" color="crimson"/>
Output
First renders "Member" with gray background. Second renders "Admin" with crimson.
Note Use default parameter values in the destructuring rather than the legacy defaultProps static property, which is deprecated in React 19.
Note Composition via props is preferred over inheritance. Passing components as props gives you flexible "slot" patterns without tightly coupling parent and child.
Returns null when no message. Otherwise renders an alert with type prefix.
Note Returning null renders nothing. Watch out with &&: if the left side is 0, React renders "0" because it is a valid JSX child. Use Boolean(count) && <Component /> or count > 0 && <Component /> instead.
Note Keys must be stable, unique among siblings, and derived from data (like a database ID). Never use array index as key if the list order can change -- it causes subtle rendering bugs and broken state.
render listmap arraylist keyrender arraydynamic list
Fragments
Syntax
<React.Fragment>...</React.Fragment>// Short syntax<>...</>
Note Fragments let you group elements without adding extra DOM nodes. Use the long syntax <React.Fragment key={...}> when mapping a list, since the short syntax does not support the key attribute.
fragmentgroup elementsno wrapper divmultiple elementsadjacent elements
Note JSX is syntactic sugar for function calls. Every tag must be closed. Use className instead of class, htmlFor instead of for. Attribute names follow camelCase convention.
jsx syntaxjsx rulesjsx basicswrite jsx
Embedding Expressions
Syntax
{expression}// Any valid JavaScript expression inside curly braces
Note You can embed any JavaScript expression inside curly braces: variables, function calls, math, ternaries. Statements (if, for, while) cannot appear inside JSX directly -- use them before the return.
jsx expressionembed javascriptcurly bracesdynamic content in jsx
Conditional Rendering Patterns
Syntax
// AND pattern{isLoggedIn &&<Dashboard/>}// Ternary{isLoggedIn ?<Dashboard/>:<LoginForm/>}// IIFE for complex logic{(()=>{if(x)return<A/>;return<B/>;})()}
Example
functionStatusBadge({ status }){return(<span>{status ==="active"&&<span className="dot green"/>}{status ==="inactive"?(<span className="label">Away</span>):(<span className="label">{status}</span>)}</span>);}
Note For more than two branches, extract the logic into a variable or helper function above the return statement rather than nesting ternaries. Readable code wins over clever one-liners.
functionFormField({ label,...inputProps}){return(<div className="field"><label>{label}</label><input {...inputProps}/></div>);}// Usage: all standard input attributes are forwarded<FormField
label="Email"type="email"
placeholder="[email protected]"
required
/>
Note Spread is great for forwarding props. Be careful: it can pass unintended attributes to DOM elements. Destructure known props first, then spread the rest.
spread propsforward propspass all propsrest propsproxy component
JSX vs createElement
Syntax
// JSX (compiled to createElement)<Button color="blue">Save</Button>// Equivalent createElement callReact.createElement(Button,{ color:"blue"},"Save")
Example
// This JSX:const element =(<div className="container"><h1>Title</h1><p>Paragraph</p></div>);// Compiles to:const element =React.createElement("div",{ className:"container"},React.createElement("h1",null,"Title"),React.createElement("p",null,"Paragraph"));
Note You almost never need to call createElement directly. JSX is syntactically cleaner and the standard in the ecosystem. The JSX transform in modern setups does not require importing React at the top of every file.
jsx createElementjsx compilationwithout jsxhow jsx works
Note Style properties use camelCase (backgroundColor, not background-color). Values that need units must include them as strings ("8px"), except for unitless properties like opacity and zIndex which accept numbers.
Displays count starting at 0. Clicking increment adds 1 each time.
Note setState triggers a re-render. The state value remains the same within the current render cycle even after calling setState -- the new value appears on the next render.
useStatemanage statecomponent statestate variablebasic state
Updater Function
Syntax
setState(prevState => newState);
Example
functionStepCounter(){const[count, setCount]=useState(0);functionincrementThree(){// Each updater receives the latest pending statesetCount(prev => prev +1);setCount(prev => prev +1);setCount(prev => prev +1);}return(<button onClick={incrementThree}>Count:{count}</button>);}
Output
Each click adds 3 to the count, because each updater builds on the previous pending value.
Note Always use the updater form when the next state depends on the previous state. Writing setCount(count + 1) three times in a row would only add 1 because count is a stale snapshot from the current render.
updater functionprevious statefunctional updateincrement statestate based on previous
functionFilteredList(){// Expensive computation runs only on mount, not every renderconst[sortedItems, setSortedItems]=useState(()=>{const saved = localStorage.getItem('cachedItems');return saved ?JSON.parse(saved):[];});return<ItemList items={sortedItems}/>;}
Note Pass a function (not the result of calling it) to avoid running expensive initialization logic on every render. React only calls the initializer function during the first render and ignores it on subsequent renders.
lazy initializationexpensive initial stateinitial state functionlocalStorage state
Note Always spread the previous object to create a new copy. Directly mutating state (profile.firstName = 'X') will NOT trigger a re-render because React compares by reference.
Note Use filter to remove, map to update, and spread to add. Never use push, splice, or direct index assignment on state arrays -- those mutate the existing array instead of creating a new one.
array stateadd to arrayremove from arrayupdate array itemtodo list state
Note Separate state variables are preferred when values change independently. Combine into an object only when values logically belong together and always change at the same time.
multiple stateseparate state variablesform statewhen to combine state
useEffect(()=>{// side effectreturn()=>{/* cleanup */};},[dependencies]);
Example
import{useState,useEffect}from'react';functionDocumentTitle({ title }){useEffect(()=>{document.title= title;},[title]);return<h1>{title}</h1>;}
Note The effect runs after the component renders. The dependency array controls when it re-runs. Omit the array: runs after every render. Empty array []: runs once on mount. With values: runs when any dependency changes.
useEffectside effectafter renderlifecyclecomponent mount
Dependency Array
Syntax
useEffect(effectFn,[dep1, dep2]);// Empty array: mount onlyuseEffect(effectFn,[]);// No array: every renderuseEffect(effectFn);
Example
functionSearchResults({ query, page }){const[results, setResults]=useState([]);useEffect(()=>{// Runs whenever query or page changeslet cancelled =false;asyncfunctionsearch(){const data =awaitfetchResults(query, page);if(!cancelled)setResults(data);}search();return()=>{ cancelled =true;};},[query, page]);return<ResultsList items={results}/>;}
Note React uses Object.is to compare dependencies. Objects and arrays create new references each render, so they will always trigger the effect. Extract primitive values or memoize objects before listing them as dependencies.
Note The cleanup function runs before the effect re-executes (when dependencies change) and when the component unmounts. This prevents memory leaks from subscriptions, timers, and open connections.
Note The ignore flag prevents setting state on an unmounted component or after a stale request. In React 19, consider using the use() hook with Suspense for data fetching instead of this pattern.
functionWindowSize(){const[size, setSize]=useState({
width:window.innerWidth,
height:window.innerHeight,});useEffect(()=>{functionhandleResize(){setSize({
width:window.innerWidth,
height:window.innerHeight,});}window.addEventListener('resize', handleResize);return()=>window.removeEventListener('resize', handleResize);},[]);return<p>{size.width} x {size.height}</p>;}
Note Always remove event listeners in the cleanup to avoid duplicate handlers and memory leaks. The handler reference must be the same function for addEventListener and removeEventListener.
Note Always clear intervals and timeouts in cleanup. Use the updater form (prev => prev + 1) inside intervals to avoid stale closure issues with the state value.
// Derived state: compute during renderconst fullName = firstName +' '+ lastName;// Event response: handle in the event handlerfunctionhandleClick(){doSomething();}
Example
// BAD: useEffect to sync derived dataconst[firstName, setFirstName]=useState('Kai');const[lastName, setLastName]=useState('Chen');const[fullName, setFullName]=useState('');useEffect(()=>{setFullName(firstName +' '+ lastName);},[firstName, lastName]);// GOOD: compute directly during renderconst fullName = firstName +' '+ lastName;// BAD: useEffect for event-driven logicuseEffect(()=>{if(submitted)sendAnalytics();},[submitted]);// GOOD: call directly in the event handlerfunctionhandleSubmit(){submitForm();sendAnalytics();}
Note Overusing useEffect is one of the most common React mistakes. If you can calculate something from existing props or state, do it during render. If something should happen in response to a user action, put it in the event handler.
avoid useEffectderived stateunnecessary effectwhen not to use effectcompute during render
Note ref.current is null during the first render and gets assigned after the DOM node mounts. Never read or write ref.current during rendering -- only inside effects or event handlers.
dom refuseReffocus inputaccess domdom element reference
Mutable Values (No Re-render)
Syntax
const valueRef =useRef(initialValue);
valueRef.current= newValue;// does NOT trigger re-render
Note useRef is like an instance variable for function components. Updating .current does not cause a re-render. Use it for values you need to persist across renders without triggering UI updates: timer IDs, previous values, flags.
mutable refpersist valueno re-renderprevious valueinstance variablestore value across renders
Forwarding Refs (React 19)
Syntax
// React 19: ref is a regular propfunctionCustomInput({ ref,...props}){return<input ref={ref}{...props}/>;}// Pre-React 19 (still works):constCustomInput=forwardRef((props, ref)=>{return<input ref={ref}{...props}/>;});
Example
// React 19 style: ref is just a propfunctionFancyInput({ ref, placeholder, className }){return(<input
ref={ref}
placeholder={placeholder}
className={`fancy-input ${className}`}/>);}// Parent componentfunctionSearchBar(){const inputRef =useRef(null);return(<div><FancyInput ref={inputRef} placeholder="Search..."/><button onClick={()=> inputRef.current.focus()}>Focus</button></div>);}
Note In React 19, ref is passed as a regular prop to function components. forwardRef is no longer necessary and is considered deprecated. Existing code using forwardRef still works but should be migrated over time.
forward refref as propforwardRefpass ref to childexpose dom node
Callback Refs
Syntax
<element ref={(node)=>{// node is the DOM element or null on unmount}}/>
Example
functionMeasuredBox(){const[height, setHeight]=useState(0);const measureRef =(node)=>{if(node !==null){setHeight(node.getBoundingClientRect().height);}};return(<div><div ref={measureRef} style={{ padding:'20px'}}><p>This box has dynamic content.</p><p>Its height is measured via a callback ref.</p></div><p>Measured height:{height}px</p></div>);}
Note Callback refs fire whenever the ref attaches or detaches. In React 19, callback refs can return a cleanup function that runs when the element is removed, similar to useEffect cleanup. Useful for measuring elements or integrating third-party DOM libraries.
Note useImperativeHandle restricts what the parent can access through the ref. Expose only the methods the parent needs instead of the entire DOM node. Combine with ref-as-prop in React 19 (no forwardRef needed).
useImperativeHandleexpose methodscustom ref handleimperative apiparent control child
Note Store timer IDs in a ref so they survive re-renders without causing them. Always clear the timer on unmount via useEffect cleanup to prevent firing after the component is gone.
store timer iddebouncetimeout refclear timeoutpersist between renders
Note The default value is used only when a component reads the context but there is no matching Provider above it in the tree. Pass a realistic shape as the default so TypeScript inference and fallback behavior work correctly.
import{useState}from'react';import{ThemeContext}from'./theme-context';functionThemeProvider({ children }){const[mode, setMode]=useState('light');const contextValue ={
mode,
toggleTheme:()=>setMode(m => m ==='light'?'dark':'light'),};return(<ThemeContext.Provider value={contextValue}>{children}</ThemeContext.Provider>);}
Note Wrap the Provider at the highest point where descendant components need the value. If the value is a new object on every render, wrap it with useMemo to prevent unnecessary re-renders of consumers.
provide contextcontext providerwrap with providerpass context value
Consuming Context
Syntax
import{useContext}from'react';const value =useContext(MyContext);
Note useContext always looks for the nearest Provider above the calling component. If none is found, it uses the default value from createContext. The component re-renders whenever the provided value changes.
useContextconsume contextread contextaccess context value
functionAppProviders({ children }){const auth =useAuthState();const theme =useThemeState();const locale =useLocaleState();return(<AuthContext.Provider value={auth}><ThemeContext.Provider value={theme}><LocaleContext.Provider value={locale}>{children}</LocaleContext.Provider></ThemeContext.Provider></AuthContext.Provider>);}// Usage at app root<AppProviders><App/></AppProviders>
Note Split unrelated data into separate contexts. A single monolithic context forces every consumer to re-render when any part of the value changes, even if they only use a small slice.
// Encapsulate context + hook in a moduleconstCtx=createContext(null);exportfunctionuseMyContext(){const ctx =useContext(Ctx);if(!ctx)thrownewError('Missing Provider');return ctx;}
Example
import{ createContext,useContext,useState}from'react';constCartContext=createContext(null);exportfunctionCartProvider({ children }){const[items, setItems]=useState([]);functionaddItem(product){setItems(prev =>[...prev,{...product, qty:1}]);}functionremoveItem(id){setItems(prev => prev.filter(item => item.id!== id));}const totalItems = items.reduce((sum, i)=> sum + i.qty,0);return(<CartContext.Provider value={{ items, addItem, removeItem, totalItems }}>{children}</CartContext.Provider>);}exportfunctionuseCart(){const context =useContext(CartContext);if(!context){thrownewError('useCart must be used within a CartProvider');}return context;}
Note Exporting a custom hook that throws when the Provider is missing catches mistakes early and gives a clear error message instead of undefined values that silently break downstream.
context custom hookcontext with stateprovider patterncart contextglobal state context
Avoiding Unnecessary Re-renders
Syntax
// Memoize the value objectconst value =useMemo(()=>({ data, handler }),[data]);// Split read-only data from dispatch<DataContext.Provider value={data}><DispatchContext.Provider value={dispatch}>
Example
import{ createContext,useContext,useMemo,useState}from'react';constCountContext=createContext(null);constCountDispatchContext=createContext(null);functionCountProvider({ children }){const[count, setCount]=useState(0);// dispatch never changes identityconst increment =useMemo(()=>()=>setCount(c => c +1),[]);return(<CountContext.Provider value={count}><CountDispatchContext.Provider value={increment}>{children}</CountDispatchContext.Provider></CountContext.Provider>);}// Components reading only dispatch won't re-render when count changesfunctionIncrementButton(){const increment =useContext(CountDispatchContext);return<button onClick={increment}>+1</button>;}
Note Splitting context into a data context and a dispatch context is a powerful pattern. Components that only call actions (dispatch) will not re-render when the data changes, because the dispatch reference stays stable.
Note useMemo caches the result of a computation. React may discard the cache under memory pressure, so never rely on it for correctness -- only for performance. Do not memoize everything by default; profile first.
Note useCallback(fn, deps) is identical to useMemo(() => fn, deps). Its primary use case is passing stable function references to memoized child components (wrapped with React.memo) to prevent their re-renders.
// All values from component scope used inside must be listeduseCallback(()=>fn(a, b),[a, b]);useMemo(()=>compute(x),[x]);
Example
functionChatRoom({ roomId, serverUrl }){// Recreated only when roomId or serverUrl changeconst connect =useCallback(()=>{const ws =newWebSocket(`${serverUrl}/rooms/${roomId}`);
ws.onopen=()=>console.log('Connected to', roomId);return ws;},[roomId, serverUrl]);useEffect(()=>{const ws =connect();return()=> ws.close();},[connect]);return<div>Chat:{roomId}</div>;}
Note Missing a dependency leads to stale closures. Including an unnecessary one causes pointless recalculations. Use the exhaustive-deps lint rule (eslint-plugin-react-hooks) to catch mistakes automatically.
constMemoizedComponent=React.memo(Component);// With custom comparisonconstMemoizedComponent=React.memo(Component,(prevProps, nextProps)=>{return prevProps.id=== nextProps.id;});
Example
import{ memo,useState,useCallback}from'react';constExpensiveRow=memo(functionExpensiveRow({ item, onToggle }){// This component only re-renders if item or onToggle changereturn(<tr onClick={()=>onToggle(item.id)}><td>{item.name}</td><td>{item.value.toFixed(2)}</td></tr>);});functionDataTable({ rows }){const[selected, setSelected]=useState(newSet());const handleToggle =useCallback((id)=>{setSelected(prev =>{const next =newSet(prev);
next.has(id)? next.delete(id): next.add(id);return next;});},[]);return(<table><tbody>{rows.map(row =>(<ExpensiveRow key={row.id} item={row} onToggle={handleToggle}/>))}</tbody></table>);}
Note React.memo does a shallow comparison of props. It only helps if the component re-renders with the same props frequently. Pair it with useCallback for function props and useMemo for object/array props.
// DO memoize when:// - Computation is genuinely expensive (sorting large arrays, complex math)// - Passing callbacks/objects to React.memo children// - Value is a dependency of another hook// DON'T memoize when:// - The computation is trivial// - The component rarely re-renders// - No downstream consumer cares about reference stability
Note Memoization has a cost: it uses memory to store results and adds comparison overhead. Only apply it where profiling shows a measurable benefit. Premature memoization adds complexity without improving performance.
when to memoizeperformance optimizationshould I useMemomemoization decisionoptimize or not
Stable References for Effects
Syntax
const options =useMemo(()=>({ key: value }),[value]);useEffect(()=>{doSomething(options);},[options]);
Example
functionDataFetcher({ endpoint, filters }){// Without useMemo, a new object is created every render,// causing the effect to re-run endlesslyconst requestConfig =useMemo(()=>({
url: endpoint,
params: filters,
headers:{'Accept':'application/json'},}),[endpoint, filters]);useEffect(()=>{const controller =newAbortController();fetch(requestConfig.url,{...requestConfig,
signal: controller.signal,}).then(r => r.json()).then(setData);return()=> controller.abort();},[requestConfig]);}
Note When an object or array is a dependency of useEffect, memoize it to prevent infinite effect loops. Every render creates a new reference, and useEffect sees a 'change' even when the values inside are identical.
stable referencereferential equalityprevent infinite loopobject dependencymemoize for effect
Note Prefer useReducer over useState when you have complex state logic with multiple sub-values, or when the next state depends on the previous state in varied ways. The reducer function must be pure -- no side effects.
import{ useId }from'react';functionLabeledInput({ label,type='text'}){const id =useId();return(<div><label htmlFor={id}>{label}</label><input id={id}type={type}/></div>);}// Each instance gets a unique, stable ID<LabeledInput label="Username"/><LabeledInput label="Email"type="email"/>
Note useId generates IDs that are stable across server and client renders, preventing hydration mismatches. Never use it for keys in a list. It is meant for accessibility attributes (htmlFor, aria-describedby, etc.).
useIdunique idaccessible labelhtmlFor idssr safe id
Note useDeferredValue lets the UI stay responsive by deferring re-rendering of expensive parts. It is similar to debouncing but integrated with React's rendering scheduler. Compare the deferred and current value to show a stale indicator.
useDeferredValuedefer renderresponsive inputdebounce renderingslow list filter
Note startTransition marks state updates as non-urgent, allowing urgent updates (like typing) to interrupt them. The isPending flag lets you show loading indicators. Unlike useDeferredValue, useTransition wraps the state update itself.
Note Designed for subscribing to external data sources (browser APIs, third-party stores) in a way that is compatible with concurrent rendering. The subscribe function must accept a callback and return an unsubscribe function.
useSyncExternalStoreexternal storesubscribebrowser api hookthird party state
import{ useDebugValue, useSyncExternalStore }from'react';functionuseMediaQuery(query){const matches =useSyncExternalStore((notify)=>{const mql =window.matchMedia(query);
mql.addEventListener('change', notify);return()=> mql.removeEventListener('change', notify);},()=>window.matchMedia(query).matches,()=>false);// Shows in React DevTools next to the hookuseDebugValue(matches ?`Matches: ${query}`:`No match: ${query}`);return matches;}
Note useDebugValue only affects the React DevTools display for custom hooks. It adds no runtime behavior. Use the format function (second argument) to defer expensive string formatting until the hook is actually inspected in DevTools.
const value =use(resource);// resource can be a Promise or a Context
Example
import{ use,Suspense}from'react';functionUserGreeting({ userPromise }){// React suspends until the promise resolvesconst user =use(userPromise);return<h2>Hello,{user.name}!</h2>;}functionApp(){const userPromise =fetchCurrentUser();return(<Suspense fallback={<p>Loading user...</p>}><UserGreeting userPromise={userPromise}/></Suspense>);}
Note Unlike other hooks, use() can be called inside if statements and loops. When reading a promise, wrap the component in Suspense. The promise should be created outside the component or cached -- do not create new promises inside render.
use hookread promisesuspense datareact 19 useasync component
use() with Context
Syntax
const value =use(SomeContext);// Can be called conditionally, unlike useContext
Example
import{ use, createContext }from'react';constFeatureFlagContext=createContext({ darkMode:false});functionBanner({ showThemed }){// Reading context conditionally -- not possible with useContextif(showThemed){const{ darkMode }=use(FeatureFlagContext);return(<div style={{ background: darkMode ?'#222':'#eee'}}>Themed banner
</div>);}return<div>Standard banner</div>;}
Note use(Context) works like useContext but can be placed after early returns or inside conditionals. This is useful when you only need the context value in some code paths.
use context conditionalconditional contextuse vs useContextread context conditionally
Note Actions are async functions passed to the action prop of <form>. They receive FormData as an argument. React handles the pending state and error transitions automatically. Actions work with useActionState and useFormStatus for richer patterns.
form actionactionsasync form submitformDatareact 19 actions
Note useFormStatus must be called from a component rendered inside a <form>. It will not work if called in the same component that renders the form -- it reads from the nearest parent form. Import it from 'react-dom', not 'react'.
useFormStatusform pendingsubmit button loadingform submission statedisable while submitting
Note useOptimistic shows a temporary state while an async action is in progress. When the action completes (or fails), React reverts to the actual state. The optimistic value is automatically rolled back if the action throws.
useOptimisticoptimistic updateinstant ui feedbackoptimistic statetemporary state
Note useActionState combines action handling with state management. The action function receives the previous state as its first argument and FormData as its second. The third return value (isPending) tells you if the action is running.
useActionStateform state actionaction with stateform error handlingserver action state
ref as a Prop
Syntax
// React 19: no forwardRef neededfunctionMyComponent({ ref,...props}){return<div ref={ref}{...props}/>;}
Note React 19 passes ref as a regular prop to function components. forwardRef is deprecated -- migrate by moving ref into the props destructuring. Existing forwardRef code continues to work but will show deprecation warnings.
ref as propno forwardRefreact 19 refpass refdeprecate forwardRef
functionProductPage({ product }){return(<article><title>{product.name}|ShopApp</title><meta name="description" content={product.summary}/><link rel="canonical" href={`/products/${product.slug}`}/><h1>{product.name}</h1><p>{product.description}</p><strong>${product.price}</strong></article>);}// React hoists these tags into the <head> automatically
Note In React 19, metadata tags rendered inside components are automatically hoisted into the document <head>. This eliminates the need for third-party helmet libraries for basic metadata management. Works with SSR and client rendering.
document titlemeta tagshead metadataseo reactpage titlereact helmet alternative
functionEmailInput(){const[email, setEmail]=useState('');const isValid = email.includes('@')&& email.includes('.');return(<div><input
type="email"
value={email}
onChange={e =>setEmail(e.target.value)}
className={isValid ?'valid':'invalid'}/>{!isValid && email.length>0&&(<small>Please enter a valid email address</small>)}</div>);}
Note Controlled inputs keep React as the single source of truth. The value prop locks the input to the state. If you set value without onChange, the input becomes read-only.
controlled inputinput stateform inputtext inputinput value
Note Use defaultValue (not value) for uncontrolled inputs. The DOM holds the state. Uncontrolled inputs are simpler for cases where you only need the value at submission time. For dynamic validation or conditional logic, controlled inputs are better.
uncontrolled inputdefaultValueref inputsimple formno state input
// Traditional approachfunctionFeedbackForm(){const[message, setMessage]=useState('');const[submitted, setSubmitted]=useState(false);asyncfunctionhandleSubmit(e){
e.preventDefault();awaitsendFeedback(message);setSubmitted(true);setMessage('');}if(submitted)return<p>Thank you for your feedback!</p>;return(<form onSubmit={handleSubmit}><textarea
value={message}
onChange={e =>setMessage(e.target.value)}
required
/><button type="submit">Submit</button></form>);}
Note With onSubmit, always call e.preventDefault() to stop the browser from reloading the page. With React 19 form actions, preventDefault is handled automatically. Both patterns are valid -- actions are newer and reduce boilerplate.
form submithandle submitprevent defaultform onSubmitsend form data
Validation Patterns
Syntax
// Validate on change, blur, or submitconst errors ={};if(!value) errors.field='Required';
Example
functionSignupForm(){const[fields, setFields]=useState({ name:'', email:'', age:''});const[errors, setErrors]=useState({});const[touched, setTouched]=useState({});functionvalidate(data){const errs ={};if(!data.name.trim()) errs.name='Name is required';if(!data.email.includes('@')) errs.email='Invalid email';if(Number(data.age)<18) errs.age='Must be at least 18';return errs;}functionhandleChange(field, value){const updated ={...fields,[field]: value };setFields(updated);if(touched[field]){setErrors(validate(updated));}}functionhandleBlur(field){setTouched(prev =>({...prev,[field]:true}));setErrors(validate(fields));}functionhandleSubmit(e){
e.preventDefault();const errs =validate(fields);setErrors(errs);setTouched({ name:true, email:true, age:true});if(Object.keys(errs).length===0){submitSignup(fields);}}return(<form onSubmit={handleSubmit}><input
value={fields.name}
onChange={e =>handleChange('name', e.target.value)}
onBlur={()=>handleBlur('name')}/>{touched.name&& errors.name&&<span>{errors.name}</span>}{/* repeat for other fields */}<button type="submit">SignUp</button></form>);}
Note Validate on blur for a good UX: users see errors after leaving a field, not while typing. On submit, validate everything and mark all fields as touched. For complex forms, consider a form library or useReducer.
form validationvalidate inputerror messagesrequired fieldonBlur validation
Multiple Inputs with One Handler
Syntax
functionhandleChange(e){const{ name, value }= e.target;setForm(prev =>({...prev,[name]: value }));}
Note Use the name attribute on inputs and a computed property name ([name]) to handle many fields with one function. This scales well and avoids writing separate handlers for each field.
multiple inputssingle handlerdynamic formcomputed property namemany form fields
Note For checkboxes, read e.target.checked (boolean), not e.target.value. For selects, value goes on the <select> element, not on individual <option> elements. Radio buttons in the same group share the same name attribute.
select dropdowncheckboxradio buttonform controlschecked state
Note Pass a function reference, not a function call. Writing onClick={handler()} would call the function during render. Use an arrow function when you need to pass arguments: onClick={() => handler(id)}.
Note React's onChange fires on every keystroke, not just when the field loses focus (which is the native DOM behavior). This makes it ideal for real-time validation and derived values.
Note Always call e.preventDefault() in onSubmit to prevent the browser from performing a full page reload. The submit event fires when pressing Enter inside an input or clicking a submit button.
functionhandler(event){
event.target// the DOM element
event.currentTarget// element the handler is attached to
event.preventDefault()
event.stopPropagation()
event.nativeEvent// underlying browser event}
Example
functionLinkInterceptor({ children }){functionhandleClick(e){// Check if it's an internal linkconst href = e.target.closest('a')?.getAttribute('href');if(href?.startsWith('/')){
e.preventDefault();navigate(href);// client-side navigation}}return<div onClick={handleClick}>{children}</div>;}
Note React wraps native events in SyntheticEvent objects that work identically across browsers. As of React 17+, events are no longer pooled, so you can access event properties asynchronously without calling e.persist().
functionDropZone({ onFileDrop }){functionhandleDragOver(e){
e.preventDefault();// Required to allow drop}functionhandleDrop(e){
e.preventDefault();
e.stopPropagation();const files =Array.from(e.dataTransfer.files);onFileDrop(files);}return(<div
onDragOver={handleDragOver}
onDrop={handleDrop}
className="drop-zone">Drop files here
</div>);}
Note Use preventDefault for links, form submissions, and drag-and-drop. Use stopPropagation sparingly -- it makes debugging harder. Prefer checking the event target to decide whether to act instead of stopping propagation.
preventDefaultstopPropagationprevent defaultstop bubblingdrag and drop
Capture Phase & Delegation
Syntax
// Capture phase (fires before children)<div onClickCapture={handler}>...</div>// Event delegation: one handler on parent<ul onClick={handleItemClick}><li data-id="1">A</li><li data-id="2">B</li></ul>
Note Event delegation (one handler on a parent) reduces the number of event listeners and works well for dynamic lists. Use data-* attributes or closest() to identify which child triggered the event. React internally uses delegation at the root already.
Note Custom hooks must start with 'use' so React can enforce the rules of hooks. They let you extract and share stateful logic between components without changing the component hierarchy.
custom hookextract logicreusable hookshare state logicuseLocalStorage
Compound Components
Syntax
functionParent({ children }){...}Parent.Child=functionChild(props){...};
Note Compound components share implicit state via context, letting you create flexible APIs like <Select> + <Select.Option>. The parent manages state and children consume it without prop drilling.
compound componentscomponent apishared state childrenaccordion patternselect option pattern
Render Props
Syntax
functionDataProvider({ render }){const data =useData();returnrender(data);}// or via children<DataProvider>{(data)=><View data={data}/>}</DataProvider>
Example
functionMouseTracker({ children }){const[position, setPosition]=useState({ x:0, y:0});useEffect(()=>{functionhandleMove(e){setPosition({ x: e.clientX, y: e.clientY});}window.addEventListener('mousemove', handleMove);return()=>window.removeEventListener('mousemove', handleMove);},[]);returnchildren(position);}// Usage<MouseTracker>{({ x, y })=>(<div>Cursorisat({x},{y})</div>)}</MouseTracker>
Note Render props are largely replaced by custom hooks, which are simpler. Still useful when you need to control what renders based on shared logic without creating a custom hook for every case.
render propsfunction as childrender callbackshared behavior
Error Boundaries
Syntax
// Error boundaries must be class components (as of React 19)classErrorBoundaryextendsReact.Component{
state ={ hasError:false};staticgetDerivedStateFromError(error){return{ hasError:true};}componentDidCatch(error, info){logError(error, info);}render(){if(this.state.hasError)return<Fallback/>;returnthis.props.children;}}
Example
classAppErrorBoundaryextendsReact.Component{constructor(props){super(props);this.state={ hasError:false, error:null};}staticgetDerivedStateFromError(error){return{ hasError:true, error };}componentDidCatch(error, errorInfo){reportToService(error, errorInfo.componentStack);}render(){if(this.state.hasError){return(<div className="error-screen"><h1>Something went wrong</h1><button onClick={()=>this.setState({ hasError:false})}>Try again
</button></div>);}returnthis.props.children;}}// Wrap sections of the UI<AppErrorBoundary><Dashboard/></AppErrorBoundary>
Note Error boundaries catch errors during rendering, in lifecycle methods, and in constructors of the tree below them. They do NOT catch errors in event handlers, async code, or server-side rendering. Use try/catch for those.
Note Portals render children into a different DOM node outside the parent hierarchy, but events still bubble through the React tree (not the DOM tree). Perfect for modals, tooltips, and dropdowns that need to escape overflow:hidden or z-index contexts.
import{ lazy,Suspense,useState}from'react';constAdminPanel=lazy(()=>import('./AdminPanel'));constUserDashboard=lazy(()=>import('./UserDashboard'));functionApp({ role }){return(<Suspense fallback={<div className="spinner">Loading...</div>}>{role ==='admin'?<AdminPanel/>:<UserDashboard/>}</Suspense>);}
Note React.lazy only works with default exports. For named exports, create an intermediate module that re-exports as default. Suspense can also wrap components that use the use() hook to read promises in React 19.
Note This pattern provides Redux-like state management with built-in React APIs. Splitting state and dispatch into separate contexts prevents components that only dispatch actions from re-rendering when state changes.
functionwithFeature(WrappedComponent){returnfunctionEnhancedComponent(props){const data =useFeatureData();return<WrappedComponent{...props} data={data}/>;};}
Note HOCs were the primary code reuse pattern before hooks. Custom hooks are now preferred for most cases because they are simpler and avoid the wrapper component nesting. HOCs are still useful for cross-cutting concerns like auth guards.
higher order componentHOCwrap componentwithAuthenhance component
// Move shared state to the nearest common ancestorfunctionParent(){const[shared, setShared]=useState(init);return(<><ChildA value={shared}/><ChildB onChange={setShared}/></>);}
Note When two components need the same data, move the state to their closest shared parent and pass it down as props. This is the fundamental React data flow pattern.
lift state upshared statesibling communicationcommon ancestorstate in parent
Prop Drilling Solutions
Syntax
// 1. Context// 2. Composition (pass components, not data)// 3. Component composition with children
Example
// PROBLEM: drilling "user" through many layers// <App user={user}> -> <Layout user> -> <Header user> -> <Avatar user>// SOLUTION 1: ContextconstUserContext=createContext(null);// provide at top, consume at Avatar// SOLUTION 2: Composition -- pass the rendered elementfunctionApp({ user }){const avatar =<Avatar user={user}/>;return<Layout header={<Header avatar={avatar}/>}/>;}// Now Layout and Header don't need to know about "user" at allfunctionLayout({ header }){return(<div><nav>{header}</nav><main>...</main></div>);}functionHeader({ avatar }){return<div className="header">{avatar}</div>;}
Note Before reaching for context, try component composition. Passing pre-rendered elements as props skips intermediate layers entirely and keeps the data flow explicit. Context is better for truly global data (theme, auth, locale).
prop drillingpass data deepavoid prop drillingcomponent compositioncontext vs composition
Context for Global State
Syntax
// Create -> Provide -> ConsumeconstCtx=createContext(defaultValue);<Ctx.Provider value={value}>...const value =useContext(Ctx);
Example
// Locale context for internationalizationconstLocaleContext=createContext('en');functionLocaleProvider({ children }){const[locale, setLocale]=useState('en');const translations =useMemo(()=>loadTranslations(locale),[locale]);const value =useMemo(()=>({ locale, setLocale, t:(key)=> translations[key]|| key }),[locale, translations]);return(<LocaleContext.Provider value={value}>{children}</LocaleContext.Provider>);}functionuseLocale(){returnuseContext(LocaleContext);}// Usage in any componentfunctionWelcomeBanner(){const{ t, locale }=useLocale();return<h1>{t('welcome_message')}({locale})</h1>;}
Note Context is ideal for low-frequency updates like theme, locale, and auth. For high-frequency updates (e.g., mouse position), context can cause excessive re-renders -- consider useSyncExternalStore or a dedicated state library.
global statecontext state managementlocale contexti18n reactapp-wide state
When to Use External State
Syntax
// Consider external state management when:// - Many components read/write the same state// - State updates are frequent and complex// - You need middleware (logging, persistence, devtools)// - Server state caching is needed (React Query, SWR)
Example
// Signs you might need a state library:// 1. Context re-renders are a bottleneck// function App() {// return (// <StoreProvider> {/* every consumer re-renders on any change */}// <BigComponentTree />// </StoreProvider>// );// }// 2. You need selectors (subscribe to a slice)// const count = useStore(state => state.count);// 3. You need server state cache with stale-while-revalidate// const { data, isLoading } = useQuery('users', fetchUsers);// Popular choices:// - Zustand: lightweight, hook-based, selectors// - Jotai: atomic state, bottom-up approach// - React Query / TanStack Query: server state management// - Redux Toolkit: complex client state with devtools
Note Start with useState and useReducer + context. Only add an external library when you hit a concrete pain point: performance issues from context re-renders, complex async logic, or the need for devtools and middleware.
external statestate libraryzustandreduxreact querywhen to use state management
Derived State
Syntax
// Compute from existing state during renderconst derivedValue =computeFrom(stateA, stateB);// Do NOT store derived values in separate state
Example
functionShoppingCart({ items }){// GOOD: derive during renderconst subtotal = items.reduce((sum, item)=> sum + item.price* item.qty,0);const tax = subtotal *0.08;const total = subtotal + tax;const itemCount = items.reduce((sum, item)=> sum + item.qty,0);return(<div><p>{itemCount} items</p><p>Subtotal: ${subtotal.toFixed(2)}</p><p>Tax: ${tax.toFixed(2)}</p><p>Total: ${total.toFixed(2)}</p></div>);// BAD: storing derived data in state// const [total, setTotal] = useState(0);// useEffect(() => setTotal(subtotal + tax), [subtotal, tax]);}
Note If you can compute a value from existing state or props, derive it inline during render instead of syncing it with useState + useEffect. Derived state in useEffect causes an extra render cycle and introduces potential bugs.
derived statecomputed valuecalculate from stateavoid redundant statesingle source of truth
State Colocation
Syntax
// Keep state as close to where it is used as possible// Move state DOWN if only one child needs it// Move state UP only when siblings need to share it
Example
// BAD: state lives too highfunctionApp(){const[searchQuery, setSearchQuery]=useState('');return(<Layout><Header/><SearchPanel query={searchQuery} setQuery={setSearchQuery}/><Footer/></Layout>);}// GOOD: state lives in the component that uses itfunctionApp(){return(<Layout><Header/><SearchPanel/><Footer/></Layout>);}functionSearchPanel(){const[searchQuery, setSearchQuery]=useState('');const results =useSearchResults(searchQuery);return(<div><input value={searchQuery} onChange={e =>setSearchQuery(e.target.value)}/><ResultsList results={results}/></div>);}
Note Colocating state reduces unnecessary re-renders and makes components easier to understand. Only lift state when it genuinely needs to be shared. Global state for local concerns is an anti-pattern that hurts performance and readability.
state colocationwhere to put statelocal statestate placementstate organization
Note Inline styles use camelCase property names and string/number values. They cannot handle pseudo-classes (:hover), media queries, or keyframe animations. Best for truly dynamic values that change at runtime.
Note CSS Modules scope class names locally by default, preventing naming collisions. The imported styles object maps your authored class names to unique generated ones. Supported out of the box by most React build tools.
functionNavLink({ href, isActive, children }){// Template literal approachconst className =`nav-link ${isActive ?'nav-link--active':''}`;return<a href={href} className={className}>{children}</a>;}// Helper function for multiple conditional classesfunctionclassNames(...classes){return classes.filter(Boolean).join(' ');}functionCard({ featured, compact, className }){return(<div
className={classNames('card',
featured &&'card--featured',
compact &&'card--compact',
className
)}>{/* ... */}</div>);}
Note Use a small classNames helper or the popular 'clsx' package to compose conditional classes cleanly. Avoid complex ternary chains inside className attributes -- extract the logic to keep JSX readable.
// With clsx or classnames libraryimport clsx from'clsx';<div className={clsx('base',{ active: isActive, disabled: isDisabled })}/>
Example
// Using the clsx libraryimport clsx from'clsx';functionStatusChip({ status }){return(<span
className={clsx('chip',{'chip--success': status ==='active','chip--warning': status ==='pending','chip--danger': status ==='error','chip--muted': status ==='inactive',})}>{status}</span>);}// Without a libraryfunctionStatusChipManual({ status }){const chipClass =['chip',
status ==='active'&&'chip--success',
status ==='error'&&'chip--danger',].filter(Boolean).join(' ');return<span className={chipClass}>{status}</span>;}
Note The clsx library (or classnames) accepts strings, objects, and arrays. Object keys are included when their value is truthy. It is lightweight (~228 bytes) and widely used in the React ecosystem.
Note CSS-in-JS co-locates styles with components and supports dynamic props, theming, and automatic vendor prefixing. The tradeoff is runtime overhead and larger bundle size. Consider CSS Modules or Tailwind for better performance in critical paths.
Note Utility-first CSS like Tailwind works well with React's component model -- styles are scoped per component by nature. Use clsx for conditional classes. Extract repeated patterns into components rather than creating custom CSS utility classes.
constMemoized=React.memo(Component);// Re-renders only when props change (shallow comparison)
Example
import{ memo }from'react';constChatMessage=memo(functionChatMessage({ author, text, timestamp }){// This only re-renders when author, text, or timestamp changereturn(<div className="message"><strong>{author}</strong><p>{text}</p><time>{newDate(timestamp).toLocaleTimeString()}</time></div>);});// Parent re-renders frequently (new messages arrive)// but existing ChatMessage components skip re-renderingfunctionChatFeed({ messages }){return(<div>{messages.map(msg =>(<ChatMessage
key={msg.id}
author={msg.author}
text={msg.text}
timestamp={msg.timestamp}/>))}</div>);}
Note React.memo only does a shallow prop comparison. If you pass objects or functions that are recreated each render, memo has no effect. Pair with useMemo/useCallback for those props.
Note Route-based code splitting gives the biggest bundle size reduction because entire page components and their dependencies are loaded on demand. Place the Suspense boundary around the lazy components, not the entire app.
// 1. Move state down (closer to where it is used)// 2. Lift content up (pass components as children)// 3. Memoize with React.memo + useCallback/useMemo
Example
// PROBLEM: typing in input re-renders the expensive listfunctionBadPage(){const[query, setQuery]=useState('');return(<div><input value={query} onChange={e =>setQuery(e.target.value)}/><ExpensiveList/>{/* re-renders on every keystroke */}</div>);}// FIX 1: Move state down into its own componentfunctionGoodPage(){return(<div><SearchInput/>{/* state lives here now */}<ExpensiveList/>{/* no longer re-renders on keystrokes */}</div>);}functionSearchInput(){const[query, setQuery]=useState('');return<input value={query} onChange={e =>setQuery(e.target.value)}/>;}// FIX 2: Lift content up via childrenfunctionInputWrapper({ children }){const[query, setQuery]=useState('');return(<div><input value={query} onChange={e =>setQuery(e.target.value)}/>{children}{/* children don't re-render */}</div>);}functionPage(){return(<InputWrapper><ExpensiveList/></InputWrapper>);}
Note Before reaching for React.memo, try restructuring your component tree. Moving state down and lifting content up are free optimizations that often eliminate the problem entirely.
avoid re-rendersreduce rendersmove state downlift content uprender optimization
Key Prop for Efficient Updates
Syntax
// Stable keys help React identify which items changed<Item key={item.id}/>// Reset component state by changing the key<Form key={selectedUserId}/>
Example
// Using key to reset component statefunctionUserEditor({ userId }){// When userId changes, React destroys the old Form// and mounts a fresh one with clean statereturn<ProfileForm key={userId} userId={userId}/>;}functionProfileForm({ userId }){const[name, setName]=useState('');const[bio, setBio]=useState('');useEffect(()=>{fetchUser(userId).then(user =>{setName(user.name);setBio(user.bio);});},[userId]);return(<form><input value={name} onChange={e =>setName(e.target.value)}/><textarea value={bio} onChange={e =>setBio(e.target.value)}/></form>);}
Note Changing a component's key forces React to unmount and remount it, resetting all internal state. This is a clean way to 'reset' a form or component when switching between items without manually clearing each state variable.
key propreset componentkey optimizationforce remountchange key reset state
import{Profiler}from'react';functiononRenderCallback(
id,
phase,// 'mount' or 'update'
actualDuration,// time spent rendering
baseDuration,// estimated time without memoization
startTime,
commitTime
){console.log(`${id} [${phase}]: ${actualDuration.toFixed(1)}ms`);}functionApp(){return(<Profiler id="ProductList" onRender={onRenderCallback}><ProductList/></Profiler>);}
Note Use the Profiler component or React DevTools Profiler tab to identify slow renders. Focus on components with high actualDuration. The baseDuration shows the worst-case cost without any memoization.
// Render only visible items in long lists// Use react-window or react-virtuoso
Example
// Concept: only render items in the viewportimport{FixedSizeList}from'react-window';functionVirtualizedUserList({ users }){constRow=({ index, style })=>(<div style={style} className="user-row"><span>{users[index].name}</span><span>{users[index].email}</span></div>);return(<FixedSizeList
height={500}
itemCount={users.length}
itemSize={50}
width="100%">{Row}</FixedSizeList>);}// Renders ~10-15 visible rows instead of 10,000
Note For lists with hundreds or thousands of items, virtualization dramatically improves performance by only rendering visible rows. react-window is lightweight; react-virtuoso supports dynamic heights. Measure first -- small lists do not need this.
// PROBLEM: handler captures old state value// FIX: use updater function or ref
Example
functionBrokenCounter(){const[count, setCount]=useState(0);useEffect(()=>{const id =setInterval(()=>{// BUG: count is always 0 (captured from initial render)setCount(count +1);},1000);return()=>clearInterval(id);},[]);// empty deps = closure captures initial countreturn<p>{count}</p>;// stuck at 1}functionFixedCounter(){const[count, setCount]=useState(0);useEffect(()=>{const id =setInterval(()=>{// FIX: updater function always has the latest valuesetCount(prev => prev +1);},1000);return()=>clearInterval(id);},[]);return<p>{count}</p>;// increments correctly}
Note Stale closures happen when a callback captures a state value that never updates. Common in setInterval, setTimeout, and event listeners set up in useEffect. Use the updater form of setState or store values in a ref.
stale closureold state valueclosure bugsetInterval stalestate not updating
Direct State Mutation
Syntax
// BAD: mutating state directly
state.items.push(newItem);setState(state);// GOOD: create a new referencesetState({...state, items:[...state.items, newItem]});
Example
functionBrokenTags(){const[tags, setTags]=useState(['react','hooks']);functionaddTag(tag){// BUG: push mutates the existing array
tags.push(tag);setTags(tags);// same reference, React sees no change}return(<div>{tags.map(t =><span key={t}>{t}</span>)}<button onClick={()=>addTag('new')}>Add</button></div>);}functionFixedTags(){const[tags, setTags]=useState(['react','hooks']);functionaddTag(tag){// CORRECT: spread creates a new arraysetTags(prev =>[...prev, tag]);}return(<div>{tags.map(t =><span key={t}>{t}</span>)}<button onClick={()=>addTag('new')}>Add</button></div>);}
Note React uses reference equality (Object.is) to decide whether to re-render. Mutating an object or array in place keeps the same reference, so React skips the update. Always produce a new object/array via spread, map, filter, or slice.
state mutationmutate state directlypush state arraystate not updatingimmutable update
Missing or Bad Keys
Syntax
// BAD: index as key with reorderable list{items.map((item, i)=><Item key={i}/>)}// GOOD: stable unique identifier{items.map(item =><Item key={item.id}/>)}
Example
// BUG: using index as key with a removable listfunctionBrokenList(){const[items, setItems]=useState([{ id:'a', text:'First'},{ id:'b', text:'Second'},{ id:'c', text:'Third'},]);functionremoveFirst(){setItems(prev => prev.slice(1));}return(<div>{/* Using index: after removing first item,
React thinks items shifted, inputs lose state */}{items.map((item, index)=>(<div key={index}><input defaultValue={item.text}/></div>))}<button onClick={removeFirst}>RemoveFirst</button></div>);}// FIX: use item.id as key// {items.map(item => <div key={item.id}>...)}
Note Index keys cause broken behavior when items are added, removed, or reordered. React associates state (including uncontrolled input values) with the key. When keys shift, state gets attached to the wrong item.
key propmissing keyindex as keylist key bugwrong keykey warning
useEffect Dependency Issues
Syntax
// MISSING DEPS: effect uses stale valuesuseEffect(()=>{fn(a, b);},[]);// a, b missing// OBJECT DEPS: infinite loopuseEffect(()=>{...},[{ x:1}]);// new ref each render
Example
// BUG: missing dependencyfunctionGreeting({ name }){const[message, setMessage]=useState('');useEffect(()=>{// name is used but not in deps -- stale valuesetMessage(`Hello, ${name}!`);},[]);// should be [name]return<p>{message}</p>;}// BUG: object dependency causes infinite loopfunctionDataLoader({ filters }){useEffect(()=>{fetchData(filters);},[filters]);// if parent recreates filters object each render, infinite loop!// FIX: destructure primitive valuesconst{ category, minPrice }= filters;useEffect(()=>{fetchData({ category, minPrice });},[category, minPrice]);}
Note Enable the eslint-plugin-react-hooks exhaustive-deps rule to catch missing dependencies. For object dependencies, extract primitive values or memoize the object in the parent. Never suppress the lint rule without understanding why.
// CAUSE 1: setState during renderfunctionBad(){const[x, setX]=useState(0);setX(1);// triggers re-render during render!}// CAUSE 2: object/array in useEffect depsuseEffect(()=>{...},[{ a:1}]);// new ref every render
Example
// BUG: calling setState unconditionally during renderfunctionBrokenComponent({ data }){const[processed, setProcessed]=useState(null);// This runs on every render, which triggers a new render...setProcessed(transform(data));return<Display data={processed}/>;}// FIX: derive the value without statefunctionFixedComponent({ data }){const processed =transform(data);// compute inlinereturn<Display data={processed}/>;}// BUG: onClick={handler()} calls immediatelyfunctionBrokenButton(){const[count, setCount]=useState(0);// handler() executes during render, calling setCount, loopingreturn<button onClick={setCount(count +1)}>Click</button>;}// FIX: pass function referencefunctionFixedButton(){const[count, setCount]=useState(0);return<button onClick={()=>setCount(count +1)}>Click</button>;}
Note Infinite re-render loops crash your app. The most common causes: (1) calling setState directly in the render body, (2) onClick={fn()} instead of onClick={fn}, (3) useEffect with an object/array literal in the dependency array that creates a new reference every render.
infinite re-rendertoo many rendersrender loopmaximum update depthsetState during render
Overusing useEffect
Syntax
// ANTI-PATTERN: effect to sync derived stateuseEffect(()=>{setB(computeFrom(a));},[a]);// BETTER: compute inlineconst b =computeFrom(a);
Example
// BAD: useEffect chainfunctionPriceCalculator({ items }){const[subtotal, setSubtotal]=useState(0);const[tax, setTax]=useState(0);const[total, setTotal]=useState(0);useEffect(()=>{setSubtotal(items.reduce((s, i)=> s + i.price,0));},[items]);useEffect(()=>{setTax(subtotal *0.1);},[subtotal]);useEffect(()=>{setTotal(subtotal + tax);},[subtotal, tax]);// 3 extra renders for what should be 0!return<p>Total: ${total.toFixed(2)}</p>;}// GOOD: calculate everything inlinefunctionPriceCalculator({ items }){const subtotal = items.reduce((s, i)=> s + i.price,0);const tax = subtotal *0.1;const total = subtotal + tax;return<p>Total: ${total.toFixed(2)}</p>;}
Note Each unnecessary useEffect + setState causes an extra render cycle. If a value can be derived from props or state, compute it during render. Reserve useEffect for genuine side effects: API calls, subscriptions, DOM manipulation, and third-party library integration.
overuse useEffectuseEffect anti-patterneffect chainderived state effectunnecessary effect
Setting State After Unmount
Syntax
// PROBLEM: async callback sets state on unmounted componentuseEffect(()=>{fetchData().then(data =>setState(data));// may be unmounted!},[]);// FIX: track mounted statususeEffect(()=>{let active =true;fetchData().then(data =>{if(active)setState(data);});return()=>{ active =false;};},[]);
Example
functionUserProfile({ userId }){const[user, setUser]=useState(null);useEffect(()=>{let cancelled =false;asyncfunctionloadUser(){const response =awaitfetch(`/api/users/${userId}`);const data =await response.json();// Only update state if this effect is still activeif(!cancelled){setUser(data);}}loadUser();return()=>{
cancelled =true;};},[userId]);if(!user)return<p>Loading...</p>;return<h1>{user.name}</h1>;}
Note While React 18+ no longer warns about this, it is still a logic bug. Without the cancelled flag, a slow request for user A could resolve after switching to user B, overwriting user B's data with user A's data. Always use a cleanup flag or AbortController.
unmount state updatecancelled requestrace conditionabort fetchcleanup async
State Batching Behavior
Syntax
// React 18+ batches ALL state updates automaticallyfunctionhandleClick(){setA(1);// does not trigger rendersetB(2);// does not trigger rendersetC(3);// single render with all three updates}
Example
functionStatusPanel(){const[loading, setLoading]=useState(false);const[data, setData]=useState(null);const[error, setError]=useState(null);asyncfunctionloadData(){setLoading(true);setError(null);// React batches these two -- one rendertry{const result =awaitfetchDashboardData();setData(result);setLoading(false);// React batches these two -- one render}catch(err){setError(err.message);setLoading(false);// React batches these two -- one render}}return(<div>{loading &&<p>Loading...</p>}{error &&<p>Error:{error}</p>}{data &&<Dashboard data={data}/>}</div>);}
Note Since React 18, automatic batching applies to all updates, including those in promises, setTimeout, and native event handlers. You rarely need to worry about batching anymore. If you ever need to force a synchronous render, use flushSync (imported from react-dom) as a last resort.
state batchingmultiple setStateautomatic batchingflushSyncrender batching