actualbudget/actual · error

Expected availableValue to be a number but got

Error message

Expected availableValue to be a number but got 

What it means

ToBudget reads the 'to budget' amount from the envelope budget sheet via useEnvelopeSheetValue. The hook may legitimately return a number or null (while loading/invalid), but any other type means the sheet cell held bad data. The component guards this invariant and throws `Expected availableValue to be a number but got <value>`. Note the empty message suffix here means the value stringified to empty (e.g. undefined coerced oddly or empty string).

Source

Thrown at packages/desktop-client/src/components/budget/envelope/budgetsummary/ToBudget.tsx:50

}: ToBudgetProps) {
  const [menuStep, _setMenuStep] = useState<string>('actions');
  const triggerRef = useRef(null);
  const format = useFormat();

  const ref = useRef<HTMLSpanElement>(null);
  const setMenuStep = useCallback(
    (menu: string) => {
      if (menu) ref.current?.focus();
      _setMenuStep(menu);
    },
    [ref, _setMenuStep],
  );
  const availableValue = useEnvelopeSheetValue({
    name: envelopeBudget.toBudget,
    value: 0,
  });
  if (typeof availableValue !== 'number' && availableValue !== null) {
    throw new Error(
      'Expected availableValue to be a number but got ' + availableValue,
    );
  }

  const [menuOpen, setMenuOpen] = useState(false);
  const [position, setPosition] = useState({ crossOffset: 0, offset: 0 });
  const resetPosition = (crossOffset = 0, offset = 0) =>
    setPosition({ crossOffset, offset });

  const handleContextMenu = (e: MouseEvent) => {
    e.preventDefault();
    const rect = e.currentTarget.getBoundingClientRect();
    setPosition({
      crossOffset: e.clientX - rect.left,
      offset: e.clientY - rect.bottom,
    });
    setMenuOpen(true);
  };

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Ensure the component only renders when the envelope sheet for the month is loaded (check isBudgetInitialized/loading state)
  2. Verify the sheetValue provider for envelopeBudget.toBudget always returns number | null
  3. Coerce defensively at the hook call site: `Number.isFinite(v) ? v : 0`
  4. Clear corrupted budget cache / re-sync the budget if stored sheet data is bad

Example fix

// before
const availableValue = useEnvelopeSheetValue({ name: envelopeBudget.toBudget, value: 0 });
// after
const raw = useEnvelopeSheetValue({ name: envelopeBudget.toBudget, value: 0 });
const availableValue = typeof raw === 'number' ? raw : null;
Defensive patterns

Strategy: type-guard

Validate before calling

const raw = useEnvelopeSheetValue({ name: envelopeBudget.toBudget, value: 0 });
if (raw !== null && typeof raw !== 'number') {
  console.warn('toBudget sheet value invalid, skipping render');
  return null;
}

Type guard

function isSheetNumber(v: unknown): v is number | null {
  return v === null || (typeof v === 'number' && Number.isFinite(v));
}

Try / catch

try {
  return <ToBudget month={month} />;
} catch (e) {
  if (String(e?.message).startsWith('Expected availableValue to be a number')) {
    return <div className="tnum">0</div>;
  }
  throw e;
}

Prevention

When it happens

Trigger: useEnvelopeSheetValue for envelopeBudget.toBudget returns a non-numeric, non-null value — typically undefined (hook not yet bound to a sheet / called outside sheet scope), an empty string cell, or a corrupted/unsynced sheet value.

Common situations: Rendering ToBudget before the budget sheet is initialized; month changed while the sheet value factory returns stale data; a bug in a custom sheet value provider returning a string; upgrading loot-core where sheet value typing changed.

Related errors


AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29). Data as JSON: /api/errors/0a2ac056795fa8cd. Report an issue: GitHub.