React Derived State and Computed Values
1. useMemo Hook for Expensive Calculations (React.useMemo)
useMemo Hook Syntax
| Feature | Syntax | Description |
|---|---|---|
| Basic Syntax | const value = useMemo(() => computation, [deps]) | Memoizes expensive calculation result |
| Return Value | Memoized value | Returns cached value if dependencies unchanged |
| Dependencies | [dep1, dep2, ...] | Recalculates only when dependencies change |
| Empty Deps | [] | Calculate once on mount |
| No Deps | undefined | ⚠️ Recalculates every render (no memoization) |
useMemo vs Recalculation Comparison
| Aspect | Without useMemo | With useMemo |
|---|---|---|
| Calculation Timing | Every render | Only when dependencies change |
| Performance | May cause unnecessary work | Optimized for expensive operations |
| Memory | Lower memory overhead | Stores cached result in memory |
| Referential Equality | New reference each render | Same reference if deps unchanged |
| Use Case | Simple/cheap calculations | Expensive computations, reference stability |
Example: Expensive Filtering with useMemo
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
Derived State Patterns
| Pattern | Implementation | Use Case |
|---|---|---|
| Direct Calculation | const derived = computeValue(state, props) | Simple derivations during render |
| useMemo Derivation | const derived = useMemo(() => compute(), [deps]) | Expensive calculations |
| Multi-source Derivation | const result = combine(state1, state2, props) | Combining multiple state sources |
| Conditional Derivation | const value = condition ? calc1() : calc2() | Different calculations based on conditions |
| ❌ Derived State Anti-pattern | useState(computeValue(props)) | DON'T store derived values in state |
Derived State vs Stored State Decision Matrix
| Criteria | Use Derived State (Calculate) | Use Stored State (useState) |
|---|---|---|
| Source of Truth | Can be computed from other values | Independent source of truth |
| Synchronization | Always in sync with dependencies | Requires manual synchronization |
| Performance | Cheap to calculate | Expensive to recalculate every render |
| Updates | Updates automatically | Must be updated explicitly |
| Example | fullName from firstName + lastName | User input, toggle state |
Example: Derived State from Multiple Sources
const ShoppingCart = ({ items, taxRate, discountCode }) => {
const [selectedItems, setSelectedItems] = useState([]);
// ✅ Derive subtotal from items (no state needed)
const subtotal = useMemo(() =>
selectedItems.reduce((sum, id) => {
const item = items.find(i => i.id === id);
return sum + (item?.price || 0);
}, 0),
[selectedItems, items]
);
// ✅ Derive discount from subtotal and code
const discount = useMemo(() => {
if (discountCode === 'SAVE20') return subtotal * 0.2;
if (discountCode === 'SAVE10') return subtotal * 0.1;
return 0;
}, [subtotal, discountCode]);
// ✅ Derive tax from subtotal after discount
const tax = useMemo(() =>
(subtotal - discount) * taxRate,
[subtotal, discount, taxRate]
);
// ✅ Derive final total
const total = subtotal - discount + tax;
return (
<div>
<p>Subtotal: ${subtotal.toFixed(2)}</p>
<p>Discount: -${discount.toFixed(2)}</p>
<p>Tax: ${tax.toFixed(2)}</p>
<p>Total: ${total.toFixed(2)}</p>
</div>
);
};
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 sync
const 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 lastName
const 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)} />;
};
3. Selector Patterns for State Selection
Selector Pattern Types
| Pattern | Implementation | Use Case |
|---|---|---|
| Simple Selector | const value = state.property | Direct property access |
| Computed Selector | const derived = computeFromState(state) | Transform or combine state values |
| Memoized Selector | useMemo(() => selectFromState(state), [deps]) | Expensive computations |
| Parametrized Selector | const selector = (id) => state.find(i => i.id === id) | Select with runtime parameters |
| Reselect Library | createSelector([deps], computation) | Advanced memoization across components |
Selector Best Practices
| Practice | Recommendation | Benefit |
|---|---|---|
| Keep Selectors Pure | No side effects, same input = same output | Predictable, testable, memoizable |
| Colocate with State | Define selectors near state definition | Easier to maintain and understand |
| Compose Selectors | Build complex selectors from simple ones | Reusability and maintainability |
| Minimize Derivations | Only derive what components need | Performance optimization |
| Use TypeScript | Type selector functions for safety | Type safety and autocomplete |
Example: Selector Pattern for Complex State
// State structure
const [state, setState] = useState({
users: [
{ id: 1, name: 'Alice', role: 'admin', active: true },
{ id: 2, name: 'Bob', role: 'user', active: false },
{ id: 3, name: 'Charlie', role: 'user', active: true }
],
filters: { role: 'user', activeOnly: true }
});
// ✅ Simple selectors
const selectUsers = (state) => state.users;
const selectFilters = (state) => state.filters;
// ✅ Computed selector (memoized)
const selectFilteredUsers = useMemo(() => {
const users = selectUsers(state);
const filters = selectFilters(state);
return users.filter(user => {
if (filters.role && user.role !== filters.role) return false;
if (filters.activeOnly && !user.active) return false;
return true;
});
}, [state.users, state.filters]);
// ✅ Parametrized selector
const selectUserById = (id) =>
state.users.find(user => user.id === id);
// ✅ Composed selector
const selectActiveAdmins = useMemo(() =>
selectUsers(state).filter(u => u.role === 'admin' && u.active),
[state.users]
);
Example: Reselect Library Pattern
import { createSelector } from 'reselect';
// Input selectors (not memoized)
const selectUsers = (state) => state.users;
const selectSearchTerm = (state) => state.searchTerm;
const selectSortOrder = (state) => state.sortOrder;
// Memoized selector - only recomputes when inputs change
const selectFilteredSortedUsers = createSelector(
[selectUsers, selectSearchTerm, selectSortOrder],
(users, searchTerm, sortOrder) => {
// Expensive filtering
const filtered = users.filter(user =>
user.name.toLowerCase().includes(searchTerm.toLowerCase())
);
// Expensive sorting
return [...filtered].sort((a, b) => {
if (sortOrder === 'asc') return a.name.localeCompare(b.name);
return b.name.localeCompare(a.name);
});
}
);
// Usage in component
const UserList = () => {
const [state, setState] = useState({/* ... */});
// Only recomputes when users, searchTerm, or sortOrder change
const users = selectFilteredSortedUsers(state);
return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
};
Note: For simple state selection, plain JavaScript is sufficient. Use libraries like Reselect
only when you need advanced memoization across multiple components.
4. Custom Hooks for Derived State Logic
Custom Hook Patterns for Derived State
| Pattern | Purpose | Example |
|---|---|---|
| useComputed | Encapsulate derived state logic | useFilteredItems(items, filter) |
| useSelector | Select and derive from complex state | useUsersByRole(state, 'admin') |
| useDebounced | Debounced derived value | useDebouncedSearch(searchTerm, 300) |
| useMemoizedValue | Reusable memoization wrapper | useMemoizedValue(compute, deps) |
| useTransform | Transform data with reusable logic | useTransform(data, transformer) |
Example: useFilteredItems Custom Hook
// ✅ Custom hook for filtered and sorted items
const useFilteredItems = (items, filterText, sortKey) => {
return useMemo(() => {
// Filter items
const filtered = items.filter(item =>
item.name.toLowerCase().includes(filterText.toLowerCase())
);
// Sort items
if (sortKey) {
return [...filtered].sort((a, b) => {
if (a[sortKey] < b[sortKey]) return -1;
if (a[sortKey] > b[sortKey]) return 1;
return 0;
});
}
return filtered;
}, [items, filterText, sortKey]);
};
// Usage in component
const ItemList = ({ items }) => {
const [filterText, setFilterText] = useState('');
const [sortKey, setSortKey] = useState('name');
// Clean, reusable derived state logic
const filteredItems = useFilteredItems(items, filterText, sortKey);
return (
<div>
<input
value={filterText}
onChange={(e) => setFilterText(e.target.value)}
placeholder="Filter..."
/>
<ul>
{filteredItems.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</div>
);
};
Example: useDebounced Custom Hook for Derived State
// ✅ Custom hook for debounced derived value
const useDebounced = (value, delay) => {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const timer = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => clearTimeout(timer);
}, [value, delay]);
return debouncedValue;
};
// Usage in search component
const SearchComponent = ({ items }) => {
const [searchTerm, setSearchTerm] = useState('');
// Debounce search term to avoid expensive filtering on every keystroke
const debouncedSearch = useDebounced(searchTerm, 300);
// Only filter when debounced value changes
const results = useMemo(() =>
items.filter(item =>
item.name.toLowerCase().includes(debouncedSearch.toLowerCase())
),
[items, debouncedSearch] // Uses debounced value, not immediate searchTerm
);
return (
<div>
<input
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
placeholder="Search..."
/>
<p>Found {results.length} results</p>
<ul>{results.map(r => <li key={r.id}>{r.name}</li>)}</ul>
</div>
);
};
Example: useSelector Pattern with Context
// State context
const StoreContext = createContext(null);
// ✅ Custom selector hook
const useSelector = (selector) => {
const state = useContext(StoreContext);
return useMemo(() => selector(state), [state, selector]);
};
// Reusable selectors
const selectUsers = (state) => state.users;
const selectActiveUsers = (state) =>
state.users.filter(u => u.active);
const selectUserById = (state, id) =>
state.users.find(u => u.id === id);
// Usage in components
const UserList = () => {
const users = useSelector(selectUsers);
return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
};
const ActiveUserCount = () => {
const activeUsers = useSelector(selectActiveUsers);
return <p>Active users: {activeUsers.length}</p>;
};
const UserDetail = ({ userId }) => {
const user = useSelector(state => selectUserById(state, userId));
return <div>{user?.name}</div>;
};
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 state
const 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 render
const 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 state
const 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 directly
const UserProfile = ({ user }) => {
return <div>{user.name}</div>;
};
// ✅ ALTERNATIVE - If you need to modify, derive initial state with key
const 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 changes
const 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
Anti-pattern #3: Premature useMemo Optimization
// ❌ BAD - Unnecessary useMemo for simple operation
const Component = ({ firstName, lastName }) => {
// ❌ Overkill - string concatenation is cheap
const fullName = useMemo(() =>
`${firstName} ${lastName}`,
[firstName, lastName]
);
return <div>{fullName}</div>;
};
// ✅ GOOD - Just calculate it
const Component = ({ firstName, lastName }) => {
const fullName = `${firstName} ${lastName}`; // Fast enough!
return <div>{fullName}</div>;
};
// ✅ USE useMemo when calculation is actually expensive
const Component = ({ items }) => {
const sortedAndFiltered = useMemo(() => {
// Expensive: filter + sort + transform large array
return items
.filter(item => item.active)
.sort((a, b) => a.timestamp - b.timestamp)
.map(item => ({ ...item, formatted: formatComplexData(item) }));
}, [items]); // Worth memoizing
return <div>{sortedAndFiltered.length} items</div>;
};
Derived State Best Practices Summary:
- Calculate during render - Don't store computable values in state
- Use useMemo selectively - Only for expensive operations (profile first)
- Avoid props in state - Use props directly or derive with key prop reset
- No useEffect for sync - Derive directly instead of syncing with effects
- Single source of truth - Keep one canonical source, derive everything else
- Custom hooks - Extract complex derivation logic for reusability
- Selector patterns - Use selectors for complex state transformations
- Test derivations - Pure derivation functions are easy to test