actualbudget/actual · error

Unrecognized menu item: ${name}

Error message

Unrecognized menu item: ${name}

What it means

BudgetMenu's onMenuSelect maps each menu item name to an optional callback prop (onSetSpread, onApplyBudgetTemplate, etc.) and throws in the default branch for unknown names. The default branch is only reachable when the items array and this handler drift out of sync.

Source

Thrown at packages/desktop-client/src/components/budget/envelope/BudgetMenu.tsx:44

  const onMenuSelect = (name: string) => {
    switch (name) {
      case 'copy-single-last':
        onCopyLastMonthAverage?.();
        break;
      case 'set-single-3-avg':
        onSetMonthsAverage?.(3);
        break;
      case 'set-single-6-avg':
        onSetMonthsAverage?.(6);
        break;
      case 'set-single-12-avg':
        onSetMonthsAverage?.(12);
        break;
      case 'apply-single-category-template':
        onApplyBudgetTemplate?.();
        break;
      default:
        throw new Error(`Unrecognized menu item: ${name}`);
    }
  };

  return (
    <Menu
      {...props}
      onMenuSelect={onMenuSelect}
      items={[
        {
          name: 'copy-single-last',
          text: t("Copy last month's budget"),
        },
        {
          name: 'set-single-3-avg',
          text: t('Set to 3 month average'),
        },
        {
          name: 'set-single-6-avg',

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Add the missing case to onMenuSelect for the item name being selected.
  2. Define a shared union type for menu item names used by both the items array and onMenuSelect, gaining compile-time exhaustiveness checking.
  3. When spreading extra props into Menu, ensure any added items also have onSelect handling or are filtered out.

Example fix

// before
default:
  throw new Error(`Unrecognized menu item: ${name}`);

// after
case 'copy-last-month-budget':
  onCopyLastMonthBudget?.();
  break;
default:
  console.warn('Unrecognized budget menu item', name);
Defensive patterns

Strategy: type-guard

Validate before calling

type BudgetMenuItem = 'set-spread' | 'check-templates' | 'apply-single-category-template' | 'copy-last-month-budget' | 'set-months-average';
const BUDGET_MENU_ITEMS: readonly BudgetMenuItem[] = ['set-spread', 'check-templates', 'apply-single-category-template', 'copy-last-month-budget', 'set-months-average'];
if (!BUDGET_MENU_ITEMS.includes(name as BudgetMenuItem)) return;

Type guard

function isBudgetMenuItem(name: string): name is BudgetMenuItem {
  return (BUDGET_MENU_ITEMS as readonly string[]).includes(name);
}

Try / catch

onMenuSelect={name => {
  try {
    dispatch(name);
  } catch (e) {
    if (String(e.message).startsWith('Unrecognized menu item')) console.warn('unhandled item', name);
    else throw e;
  }
}}

Prevention

When it happens

Trigger: Selecting (or programmatically dispatching) a menu item whose name has no case in onMenuSelect — typically after adding a new item like 'apply-single-category-template' without a matching case, or passing extra items via Menu props spread ({...props}).

Common situations: Contributors extending the budget tool menu but only editing the items list; callers overriding/adding items through props; tests simulating clicks on renamed items.

Related errors


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