const FilteredList = ({ items, filterText }) => { // ❌ Without useMemo - filters on every render const filteredItems = items.filter(item => item.name.toLowerCase().includes(filterText.toLowerCase()) ); // ✅ With useMemo - filters only when items or filterText change const memoizedItems = useMemo(() => { console.log('Filtering items...'); return items.filter(item => item.name.toLowerCase().includes(filterText.toLowerCase()) ); }, [items, filterText]); return <ul>{memoizedItems.map(item => <li key={item.id}>{item.name}</li>)}</ul>;};
Example: Complex Calculation with useMemo
const DataAnalysis = ({ data }) => { const statistics = useMemo(() => { const sum = data.reduce((acc, val) => acc + val, 0); const avg = sum / data.length; const sorted = [...data].sort((a, b) => a - b); const median = sorted[Math.floor(sorted.length / 2)]; return { sum, avg, median, count: data.length }; }, [data]); // Only recalculate when data changes return ( <div> <p>Sum: {statistics.sum}</p> <p>Average: {statistics.avg}</p> <p>Median: {statistics.median}</p> </div> );};
Note: Only use useMemo for genuinely expensive calculations. Simple operations (string
concatenation, basic math) don't benefit from memoization and add unnecessary complexity.
2. Derived State from Props and State Combinations
Anti-pattern Warning: Don't store derived values in state. This creates synchronization bugs
when source values change but derived state isn't updated.
Example: ❌ Bad - Storing Derived State
// ❌ BAD - fullName stored in state can get out of syncconst UserProfile = ({ initialFirstName, initialLastName }) => { const [firstName, setFirstName] = useState(initialFirstName); const [lastName, setLastName] = useState(initialLastName); const [fullName, setFullName] = useState(`${initialFirstName} ${initialLastName}`); // ❌ Must manually sync fullName - error-prone! const handleFirstNameChange = (e) => { setFirstName(e.target.value); setFullName(`${e.target.value} ${lastName}`); // Can forget this! }; return <input value={firstName} onChange={handleFirstNameChange} />;};// ✅ GOOD - Derive fullName from firstName and lastNameconst UserProfile = ({ initialFirstName, initialLastName }) => { const [firstName, setFirstName] = useState(initialFirstName); const [lastName, setLastName] = useState(initialLastName); // ✅ Always in sync - no manual updates needed const fullName = `${firstName} ${lastName}`; return <input value={firstName} onChange={(e) => setFirstName(e.target.value)} />;};
Note: For simple state selection, plain JavaScript is sufficient. Use libraries like Reselect
only when you need advanced memoization across multiple components.
Best Practice: Custom hooks for derived state improve code reusability, testability, and
maintainability. Extract complex derivation logic into custom hooks when it's used in multiple components.
5. Avoiding Derived State Anti-patterns
Common Derived State Anti-patterns
Anti-pattern
Problem
Solution
Storing Derived Data in State
Synchronization bugs, redundant state
Calculate derived values during render
useEffect to Sync Derived State
Unnecessary renders, complexity
Derive directly or use useMemo
Props in State (Derived from Props)
Stale values when props change
Use props directly or derive with key
Premature Memoization
Complexity without performance benefit
Profile first, optimize if needed
Over-normalization
Complex derivations for simple data
Keep data structure simple when possible
Anti-pattern Detection Checklist
Question
Red Flag (Anti-pattern)
Green Light (Good Pattern)
Can this be computed?
Using useState for computable value
Deriving value from existing state
Is useEffect updating state?
useEffect syncing derived value to state
Direct calculation or useMemo
Does state depend on props?
useState(props.value)
Use props directly or key prop reset
Is calculation expensive?
useMemo for trivial operations
Plain calculation or useMemo if profiled
Anti-pattern #1: Storing Derived Data in State
// ❌ BAD - Storing derived fullName in stateconst UserForm = () => { const [firstName, setFirstName] = useState(''); const [lastName, setLastName] = useState(''); const [fullName, setFullName] = useState(''); // ❌ Redundant state! // ❌ Must manually keep fullName in sync useEffect(() => { setFullName(`${firstName} ${lastName}`); }, [firstName, lastName]); // Extra render cycle! return <div>{fullName}</div>;};// ✅ GOOD - Derive fullName during renderconst UserForm = () => { const [firstName, setFirstName] = useState(''); const [lastName, setLastName] = useState(''); // ✅ Always in sync, no extra render const fullName = `${firstName} ${lastName}`; return <div>{fullName}</div>;};
Anti-pattern #2: Props in State (Mirror Props)
// ❌ BAD - Mirroring props in stateconst UserProfile = ({ user }) => { const [currentUser, setCurrentUser] = useState(user); // ❌ Stale when props change! // ❌ Requires useEffect to sync useEffect(() => { setCurrentUser(user); }, [user]); return <div>{currentUser.name}</div>;};// ✅ GOOD - Use props directlyconst UserProfile = ({ user }) => { return <div>{user.name}</div>;};// ✅ ALTERNATIVE - If you need to modify, derive initial state with keyconst UserProfile = ({ user }) => { const [edits, setEdits] = useState({}); // Derive current values from props + edits const currentName = edits.name ?? user.name; const currentEmail = edits.email ?? user.email; return ( <div> <input value={currentName} onChange={(e) => setEdits(prev => ({ ...prev, name: e.target.value }))} /> </div> );};// ✅ OR use key prop to reset state when user changesconst UserProfile = ({ user }) => { const [name, setName] = useState(user.name); return <input value={name} onChange={(e) => setName(e.target.value)} />;};// Usage with key<UserProfile key={user.id} user={user} /> // Remounts when user.id changes