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
- Add the missing case to onMenuSelect for the item name being selected.
- Define a shared union type for menu item names used by both the items array and onMenuSelect, gaining compile-time exhaustiveness checking.
- 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
- Define menu item names in one shared literal-union type used by items and onSelect.
- Audit {...props} spreads that can inject unhandled items into Menu.
- Write a test that clicks every rendered menu item to catch missing cases in CI.
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
- Unrecognized menu option: ${name}
- Unrecognized menu option: ${String(name)}
- Unknown item type:
- Unknown display type: ${String(displayType)}
- Unknown template type: ${String(type satisfies undefined)}
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/3c8f32e385e7f192.
Report an issue: GitHub.