{"record":{"id":"2d558d995ce67740","repo":"actualbudget/actual","slug":"internal-error-category-with-id-categoryidtomov","errorCode":null,"errorMessage":"Internal error: category with ID ${categoryIdToMove} not found.","messagePattern":"Internal error: category with ID (.+?) not found\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/desktop-client/src/components/mobile/budget/ExpenseCategoryList.tsx","lineNumber":69,"sourceCode":"          target={target}\n          className={css({\n            '&[data-drop-target]': {\n              height: 4,\n              backgroundColor: theme.tableBorderSeparator,\n              opacity: 1,\n              borderRadius: 4,\n            },\n          })}\n        />\n      );\n    },\n    onReorder: e => {\n      const [key] = e.keys;\n      const categoryIdToMove = key as CategoryEntity['id'];\n      const categoryToMove = categories.find(c => c.id === categoryIdToMove);\n\n      if (!categoryToMove) {\n        throw new Error(\n          `Internal error: category with ID ${categoryIdToMove} not found.`,\n        );\n      }\n\n      if (!categoryToMove.group) {\n        throw new Error(\n          `Internal error: category ${categoryIdToMove} is not in a group and cannot be moved.`,\n        );\n      }\n\n      const targetCategoryId = e.target.key as CategoryEntity['id'];\n\n      if (e.target.dropPosition === 'before') {\n        moveCategory.mutate({\n          id: categoryToMove.id,\n          groupId: categoryToMove.group,\n          targetId: targetCategoryId,\n        });","sourceCodeStart":51,"sourceCodeEnd":87,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/desktop-client/src/components/mobile/budget/ExpenseCategoryList.tsx#L51-L87","documentation":"ExpenseCategoryList's drag-and-drop reorder throws when the dragged category ID (`e.keys[0]`) is not found in the local `categories` array. This invariant guarantees `categoryToMove` is valid before computing its new position. It indicates the drag payload references a category that no longer exists in the rendered list.","triggerScenarios":"`onReorder` fires with a key whose category lookup (`categories.find(c => c.id === categoryIdToMove)`) returns undefined — the dragged category was deleted, filtered out, or the list was replaced between drag start and drop.","commonSituations":"Category deleted via sync or another tab mid-drag; category belongs to a different group's list but was dragged; stale component state after category recreation with a new ID.","solutions":["Refresh the categories list from the store and retry the reorder.","Confirm the drag source is bound to the same list that handles onReorder (keys must come from the rendered categories).","Return early instead of throwing when the lookup misses, treating it as a no-op stale drop.","Check for concurrent category deletion during sync."],"exampleFix":"// before\nif (!categoryToMove) {\n  throw new Error(`Internal error: category with ID ${categoryIdToMove} not found.`);\n}\n// after\nif (!categoryToMove) {\n  console.warn(`Skipping reorder: category ${categoryIdToMove} no longer exists`);\n  return;\n}","handlingStrategy":"validation","validationCode":"const [key] = e.keys;\nconst categoryToMove = categories.find(c => c.id === key);\nif (!categoryToMove) return; // stale key; skip reorder","typeGuard":"function categoryExists(categories: { id: string }[], id: string): boolean {\n  return categories.some(c => c.id === id);\n}","tryCatchPattern":"try {\n  onReorder(e);\n} catch (err) {\n  if (String(err).includes('not found')) {\n    refreshCategories();\n  } else {\n    throw err;\n  }\n}","preventionTips":["Validate drag keys against the live list before reorder logic.","Keep drag source and reorder handler bound to the same array.","Refresh list state after any delete/sync that could affect categories.","Prefer warn-and-return over throw for stale UI interactions."],"tags":["ui","drag-and-drop","state-desync","internal-error"],"backgroundTag":"stale-drop-target-id","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}