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.