actualbudget/actual · error
Internal error: category group with ID ${draggedGroupId} not
Error message
Internal error: category group with ID ${draggedGroupId} not found. What it means
ExpenseGroupList's renderDragPreview looks up the dragged group ID from the drag payload's 'text/plain' data and throws if no matching category group exists in `categoryGroups`. This guarantees the preview can render a real ExpenseGroupHeader for the dragged item.
Source
Thrown at packages/desktop-client/src/components/mobile/budget/ExpenseGroupList.tsx:74
return (
<DropIndicator
target={target}
className={css({
'&[data-drop-target]': {
height: 4,
backgroundColor: theme.tableBorderSeparator,
opacity: 1,
borderRadius: 4,
},
})}
/>
);
},
renderDragPreview: items => {
const draggedGroupId = items[0]['text/plain'];
const group = categoryGroups.find(c => c.id === draggedGroupId);
if (!group) {
throw new Error(
`Internal error: category group with ID ${draggedGroupId} not found.`,
);
}
return (
<ExpenseGroupHeader
categoryGroup={group}
month={month}
showBudgetedColumn={showBudgetedColumn}
show3Columns={show3Columns}
onEditCategoryGroup={() => {}}
isCollapsed={() => true}
onToggleCollapse={() => {}}
isHidden={false}
/>
);
},
onReorder: e => {
const [key] = e.keys;View on GitHub (pinned to d4334cb6e6)
Solutions
- Ensure the drag payload's 'text/plain' value is exactly the category group ID.
- Refresh categoryGroups so the dragged group is present before/while dragging.
- Render a fallback preview (or return null) instead of throwing when the group is missing.
- Check for concurrent group deletion via sync.
Example fix
// before
if (!group) {
throw new Error(`Internal error: category group with ID ${draggedGroupId} not found.`);
}
// after
if (!group) {
return null; // group removed mid-drag; skip preview
} Defensive patterns
Strategy: fallback
Validate before calling
const draggedGroupId = items[0]['text/plain']; const group = categoryGroups.find(c => c.id === draggedGroupId); if (!group) return null; // render no preview
Type guard
function groupExists(groups: { id: string }[], id: string): boolean {
return groups.some(g => g.id === id);
} Try / catch
try {
return renderDragPreview(items);
} catch (err) {
if (String(err).includes('not found')) {
return null; // fallback: no custom preview
}
throw err;
} Prevention
- Serialize exactly the group ID into the 'text/plain' drag payload.
- Render a default/fallback preview when lookup fails.
- Avoid deleting groups while a drag is active without refreshing drag state.
- Add a test that renders the preview after a list refresh.
When it happens
Trigger: The drag payload's `items[0]['text/plain']` contains an ID absent from `categoryGroups` — the payload ID format changed, the group was deleted mid-drag, or the preview is rendered after the list was refreshed without that group.
Common situations: Group deleted on another device while dragging; a refactor changed what is serialized into 'text/plain' so it no longer equals a group ID; preview rendering racing a list reload.
Related errors
- Internal error: account with ID ${targetAccountId} not found
- Internal error: category with ID ${categoryIdToMove} not fou
- Internal error: category ${categoryIdToMove} is not in a gro
- Internal error: category with ID ${targetCategoryId} not fou
- Internal error: category group with ID ${groupIdToMove} not
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/a8b1b51803e48498.
Report an issue: GitHub.