actualbudget/actual · error
Internal error: category with ID ${categoryIdToMove} not fou
Error message
Internal error: category with ID ${categoryIdToMove} not found. What it means
IncomeCategoryList's drag-and-drop reorder throws when the dragged category ID (`e.keys[0]`) is not found in its `categories` array, mirroring the expense list invariant. It guarantees the category exists and has a group before reordering.
Source
Thrown at packages/desktop-client/src/components/mobile/budget/IncomeCategoryList.tsx:58
target={target}
className={css({
'&[data-drop-target]': {
height: 4,
backgroundColor: theme.tableBorderSeparator,
opacity: 1,
borderRadius: 4,
},
})}
/>
);
},
onReorder: e => {
const [key] = e.keys;
const categoryIdToMove = key as CategoryEntity['id'];
const categoryToMove = categories.find(c => c.id === categoryIdToMove);
if (!categoryToMove) {
throw new Error(
`Internal error: category with ID ${categoryIdToMove} not found.`,
);
}
if (!categoryToMove.group) {
throw new Error(
`Internal error: category ${categoryIdToMove} is not in a group and cannot be moved.`,
);
}
const targetCategoryId = e.target.key as CategoryEntity['id'];
if (e.target.dropPosition === 'before') {
moveCategory.mutate({
id: categoryToMove.id,
groupId: categoryToMove.group,
targetId: targetCategoryId,
});View on GitHub (pinned to d4334cb6e6)
Solutions
- Refresh the income categories list and retry the reorder.
- Confirm the drag source keys are bound to this list's categories.
- Return early instead of throwing when the lookup misses.
- Check for concurrent category deletion during sync.
Example fix
// before
if (!categoryToMove) {
throw new Error(`Internal error: category with ID ${categoryIdToMove} not found.`);
}
// after
if (!categoryToMove) {
console.warn(`Skipping reorder: category ${categoryIdToMove} no longer exists`);
return;
} Defensive patterns
Strategy: validation
Validate before calling
const [key] = e.keys; const categoryToMove = categories.find(c => c.id === key); if (!categoryToMove) return; // stale key; skip reorder
Type guard
function categoryExists(categories: { id: string }[], id: string): boolean {
return categories.some(c => c.id === id);
} Try / catch
try {
onReorder(e);
} catch (err) {
if (String(err).includes('not found')) {
refreshCategories();
} else {
throw err;
}
} Prevention
- Validate drag keys against the live income categories list.
- Refresh list state after deletions or sync updates.
- Keep drag sources and reorder handlers bound to the same data.
- Treat missed lookups as no-ops in UI reorder flows.
When it happens
Trigger: `onReorder` fires with a key absent from `categories` — the income category was deleted, the list was replaced between drag start and drop, or the drag key came from another list.
Common situations: Category deleted via sync mid-drag; dragging between expense and income lists; stale component state after budget data reload.
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 ${groupIdToMove} not
- Internal error: category group with ID ${targetGroupId} not
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/06c57974d5bf1af1.
Report an issue: GitHub.