Good. Three leaks, clear guidance from the advisor. Writing the cleaned post now.
TLDR: Xero returns
undefinedfor amounts on untouched projects. Feed that into a currency formatter and you get$NaN. One?? 0fixes it. But the same reflex — without asking why something is undefined — can bury a much worse bug. I learned this the hard way on two projects in the same week.
The Setup
I'm building a practice management system for a law firm client, and part of it pulls project data from Xero (accounting software) to show billed and unbilled amounts on each matter.
Standard integration. Fetch the project, format the amounts, render them.
What could go wrong.
The Wall
A brand-new matter — linked Xero project, no time entries yet — and the UI renders $NaN in the amounts column.
Not $0.00.
Not a dash.
$NaN.
Because Xero returns undefined for amounts on projects that haven't been touched yet. That undefined flows straight into my currency formatter. The formatter does arithmetic on it. Arithmetic on undefined produces NaN. React renders the string "$NaN".
The whole chain is deterministic. It just looks INSANE in the UI.
The Fix
One line, at the Xero response boundary:
const amount = project.totalAmount ?? 0
Xero says "no amount yet" — I say "a project with nothing billed is correctly zero."
Coerce it, deploy it, done. $0.00 renders, client doesn't panic.
Where the Same Reflex Would Have Buried a Real Bug
A few days later, different project — an internal inventory and supply-chain system for an ecommerce business.
Two computed metrics were reading back as 0 from Supabase (my Postgres-on-top platform): run_rate_7d and run_rate_30d.
A third column, days_of_inventory, was totally fine.
I almost reached for ?? 0 again.
But something nagged at me. Two columns, both 0, both with digit segments in their names. The one that worked had none. That selectivity was too specific to be a data problem.
The real culprit: camelCaseRow, a utility that transforms snake_case DB column names to camelCase, was silently mis-mapping any column whose name included _7d or _30d. Digit segments in the key broke the transform. The returned object had the wrong key names — so the values were undefined at the call site, not in the database.
TypeScript was no help at all. The utility does result as T, so the compiler trusts the declared type and never sees the mismatch. Passing typecheck was zero evidence of correctness.
If I'd slapped ?? 0 on those reads, I'd have shipped run-rate charts showing a flat line for every SKU.
Plausible. Completely wrong.
Why This Matters to Me
?? 0 is a trust boundary decision, not just a number-coercion shortcut.
The question I ask now before reaching for it: is zero actually the truth here, or am I just papering over "I don't know why this is undefined"?
- Xero project, no time logged yet → zero is correct. Coerce it.
- Computed metric that should always have a value → zero is a lie. Find the bug.
Same operator. Same instinct. Completely opposite situations.
The one-second question is the difference between a clean fix and a silent disaster sitting in production.
P.S. The supply-chain bug's fingerprint — digit segments in the key names, consistent 0 output — is worth logging as a diagnostic pattern. When undefined is suspiciously selective, it's almost always a name-transform problem, not a data problem.