actualbudget/actual · error
Internal error: category group with ID ${groupIdToMove} not
Error message
Internal error: category group with ID ${groupIdToMove} not found. What it means
ExpenseGroupList's onReorder handler throws when the dragged group ID (`e.keys[0]`) cannot be found in `categoryGroups`. The invariant ensures the group being moved actually exists before computing its insertion position.
Source
Thrown at packages/desktop-client/src/components/mobile/budget/ExpenseGroupList.tsx:97
<ExpenseGroupHeader
categoryGroup={group}
month={month}
showBudgetedColumn={showBudgetedColumn}
show3Columns={show3Columns}
onEditCategoryGroup={() => {}}
isCollapsed={() => true}
onToggleCollapse={() => {}}
isHidden={false}
/>
);
},
onReorder: e => {
const [key] = e.keys;
const groupIdToMove = key as CategoryGroupEntity['id'];
const groupToMove = categoryGroups.find(c => c.id === groupIdToMove);
if (!groupToMove) {
throw new Error(
`Internal error: category group with ID ${groupIdToMove} not found.`,
);
}
const targetGroupId = e.target.key as CategoryEntity['id'];
if (e.target.dropPosition === 'before') {
moveCategoryGroup.mutate({
id: groupToMove.id,
targetId: targetGroupId,
});
} else if (e.target.dropPosition === 'after') {
const targetGroupIndex = categoryGroups.findIndex(
c => c.id === targetGroupId,
);
if (targetGroupIndex === -1) {
throw new Error(View on GitHub (pinned to d4334cb6e6)
Solutions
- Refresh categoryGroups from the store and retry the reorder.
- Bind the drag source keys to the same array passed to onReorder.
- Return early (no-op) when the group lookup misses instead of throwing.
- Audit for concurrent group deletions during active drags.
Example fix
// before
if (!groupToMove) {
throw new Error(`Internal error: category group with ID ${groupIdToMove} not found.`);
}
// after
if (!groupToMove) {
console.warn(`Skipping reorder: group ${groupIdToMove} no longer exists`);
return;
} Defensive patterns
Strategy: validation
Validate before calling
const [key] = e.keys; const groupToMove = categoryGroups.find(c => c.id === key); if (!groupToMove) return; // stale key; skip reorder
Type guard
function groupExists(groups: { id: string }[], id: string): boolean {
return groups.some(g => g.id === id);
} Try / catch
try {
onReorder(e);
} catch (err) {
if (String(err).includes('not found')) {
refreshCategoryGroups();
} else {
throw err;
}
} Prevention
- Validate drag keys against current categoryGroups before reordering.
- Refresh group state after budget month switches or sync events.
- Bind the drag source to the same array consumed by onReorder.
- Prefer no-op handling for stale drags over throwing.
When it happens
Trigger: `onReorder` fires with a key whose `categoryGroups.find(c => c.id === groupIdToMove)` returns undefined — group deleted concurrently, list filtered, or drag payload from a stale render.
Common situations: Group removed via sync/another tab mid-drag; dragging a group header whose list was re-rendered with fresh IDs; drag started before a budget-month switch replaced the groups.
Related errors
- Internal error: account with ID ${targetAccountId} not found
- Internal error: category with ID ${categoryIdToMove} not fou
- Internal error: category with ID ${targetCategoryId} not fou
- Internal error: category group with ID ${targetGroupId} not
- Internal error: category with ID ${categoryIdToMove} not fou
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/3e3d0ff6e1c26f52.
Report an issue: GitHub.