Derived State and Computed Values

1. useMemo Hook for Expensive Calculations

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