State Derivation

Do not store values that can be derived from props or other state; compute them on demand or memoise.

Why?  Prevents source‑of‑truth drift and reduces update cascades.

New React developers sometimes “cache” derived data in state because it feels faster or more convenient. The trap? You now have two sources of truth. Forget to keep them in sync and your UI lies to you—think stale counts, wrong filters, or layout glitches. React is already great at recomputing pure values during render; let it handle the math instead of managing more state than you need.

Correct Example

import { useMemo, useState } from "react";

type Product = { id: number; name: string; price: number };

export default function Cart({ products }: { products: Product[] }) {
  const [discount, setDiscount] = useState(0); // genuine state

  // Total is derived → compute, not store.
  const total = useMemo(
    () => products.reduce((sum, p) => sum + p.price, 0) * (1 - discount),
    [products, discount]
  );

  return (
    <section className="space-y-2">
      <h2 className="text-xl font-semibold">Cart</h2>
      {products.map((p) => (
        <p key={p.id}>
          {p.name} — ${p.price}
        </p>
      ))}
      <label className="block">
        Discount %
        <input
          type="number"
          value={discount * 100}
          onChange={(e) => setDiscount(+e.target.value / 100)}
          className="ml-2 w-20 border"
        />
      </label>
      <p className="font-bold">Total: ${total.toFixed(2)}</p>
    </section>
  );
}

Incorrect Example

// Trying to “cache” the total in state—easy to desync.
function CartBroken({ products }: { products: Product[] }) {
  const [discount, setDiscount] = useState(0);
  const [total, setTotal] = useState(0); // ❌ redundant state

  // Update total when products change (but what if we forget one?)
  useEffect(() => {
    const t = products.reduce((sum, p) => sum + p.price, 0) * (1 - discount);
    setTotal(t);
  }, [products]); // ❌ forgot to include discount!

  return (
    <>
      {/* …product list… */}
      <p>Total: ${total.toFixed(2)}</p> {/* stale after discount changes */}
    </>
  );
}

Outcome: Applying a discount doesn’t refresh total because the effect’s dependency list is incomplete—classic “derived state drift.”

Key Takeaways

  • Single source of truth: If a value can be calculated from existing props/state, keep it out of state.
  • Compute or memoize: Use plain expressions in render, or useMemo when the calculation is heavy.
  • Less state, fewer bugs: Every extra useState is a synchronization liability—eliminate the unnecessary ones.
  • Dependency safety: Derived data in useMemo/useEffect must list every input; forgetting one re‑creates drift.