Skip to content

Derived values that can't go stale

A line item: pick a quantity and a discount, show subtotal → discount → tax → total. Each number is computed from the ones before it.

The values are easy. Keeping the dependencies between them correct as the formulas change is where React quietly rots.


React — chained useMemo, hand-kept dependency arrays

Section titled “React — chained useMemo, hand-kept dependency arrays”
import { useState, useMemo } from "react"
const UNIT_PRICE = 12.5
const TAX_RATE = 0.0825
export function LineItem() {
const [quantity, setQuantity] = useState(1)
const [discountPct, setDiscountPct] = useState(0)
const subtotal = useMemo(() => quantity * UNIT_PRICE, [quantity])
const discount = useMemo(() => subtotal * (discountPct / 100), [subtotal, discountPct])
const tax = useMemo(() => (subtotal - discount) * TAX_RATE, [subtotal, discount])
const total = useMemo(() => subtotal - discount + tax, [subtotal, discount, tax])
return (
<div className="line">
<button onClick={() => setQuantity((q) => Math.max(1, q - 1))}></button>
<input type="number" value={quantity}
onChange={(e) => setQuantity(Math.max(1, Number(e.target.value) || 1))} />
<button onClick={() => setQuantity((q) => q + 1)}></button>
<p>Total: ${total.toFixed(2)}</p>
</div>
)
}

Reactra — chained derived, dependencies inferred

Section titled “Reactra — chained derived, dependencies inferred”
const UNIT_PRICE = 12.5
const TAX_RATE = 0.0825
export component LineItem {
state quantity: number = 1
state discountPct: number = 0
derived subtotal = quantity * UNIT_PRICE
derived discount = subtotal * (discountPct / 100)
derived tax = (subtotal - discount) * TAX_RATE
derived total = subtotal - discount + tax
view {
<div className="line">
<button onClick={() => setQuantity(Math.max(1, quantity - 1))}>−</button>
<input type="number" value={quantity}
onChange={(e: any) => setQuantity(Math.max(1, Number(e.target.value) || 1))} />
<button onClick={() => setQuantity(quantity + 1)}></button>
<p>Total: ${total.toFixed(2)}</p>
</div>
}
}
Compiled React 19 — this is the file in your repo
import { useEffect, useMemo, useState } from "react"
export const LineItem = () => {
const [quantity, setQuantity] = useState((1) as number)
const [discountPct, setDiscountPct] = useState((0) as number)
const subtotal = useMemo(() => quantity * UNIT_PRICE, [quantity])
const discount = useMemo(() => subtotal * (discountPct / 100), [subtotal, discountPct])
const tax = useMemo(() => (subtotal - discount) * TAX_RATE, [subtotal, discount])
const total = useMemo(() => subtotal - discount + tax, [subtotal, discount, tax])
useEffect(() => { const h = globalThis.__REACTRA_TEST__; if (h) h.update("LineItem", { quantity, discountPct, subtotal, discount, tax, total }) })
return (<>
<div className="line">
<button onClick={() => setQuantity(Math.max(1, quantity - 1))}></button>
<input type="number" value={quantity}
onChange={(e: any) => setQuantity(Math.max(1, Number(e.target.value) || 1))} />
<button onClick={() => setQuantity(quantity + 1)}></button>
<p>Total: ${total.toFixed(2)}</p>
</div>
</>)
}
export default LineItem

const total = useMemo(() => subtotal - discount + tax, [subtotal, discount]) // ← forgot `tax`

Drop one name from a dependency array and total silently stops updating when tax changes. No error, no warning — just a wrong number on a screen, found in production. Every chained useMemo is another array to keep in sync by hand.

On the right, derived total = subtotal - discount + tax has no dependency array to forget. The compiler reads the expression and tracks what it uses. The formula is the dependency list. Change the formula, the deps change with it.


  • const [x, setX] = useState(…) tuple → state x = … (one name; setX is generated)
  • 4 hand-maintained useMemo dependency arrays0
  • The “did I list every dependency?” review checklist → not a thing anymore
  • Deciding what’s worth memoizingderived always memoizes, correctly

state x = 0const [x, setX] = useState(0). derived y = x * 2const y = useMemo(() => x * 2, [x]) — with the [x] filled in by the compiler. It’s the React you’d write, minus the array you’d get wrong.

The formula is the dependency list. You only have to be right once.