{"record":{"id":"a8b1b51803e48498","repo":"actualbudget/actual","slug":"internal-error-category-group-with-id-draggedgr","errorCode":null,"errorMessage":"Internal error: category group with ID ${draggedGroupId} not found.","messagePattern":"Internal error: category group with ID (.+?) not found\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/desktop-client/src/components/mobile/budget/ExpenseGroupList.tsx","lineNumber":74,"sourceCode":"      return (\n        <DropIndicator\n          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    renderDragPreview: items => {\n      const draggedGroupId = items[0]['text/plain'];\n      const group = categoryGroups.find(c => c.id === draggedGroupId);\n      if (!group) {\n        throw new Error(\n          `Internal error: category group with ID ${draggedGroupId} not found.`,\n        );\n      }\n      return (\n        <ExpenseGroupHeader\n          categoryGroup={group}\n          month={month}\n          showBudgetedColumn={showBudgetedColumn}\n          show3Columns={show3Columns}\n          onEditCategoryGroup={() => {}}\n          isCollapsed={() => true}\n          onToggleCollapse={() => {}}\n          isHidden={false}\n        />\n      );\n    },\n    onReorder: e => {\n      const [key] = e.keys;","sourceCodeStart":56,"sourceCodeEnd":92,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/desktop-client/src/components/mobile/budget/ExpenseGroupList.tsx#L56-L92","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nif (!group) {\n  throw new Error(`Internal error: category group with ID ${draggedGroupId} not found.`);\n}\n// after\nif (!group) {\n  return null; // group removed mid-drag; skip preview\n}","handlingStrategy":"fallback","validationCode":"const draggedGroupId = items[0]['text/plain'];\nconst group = categoryGroups.find(c => c.id === draggedGroupId);\nif (!group) return null; // render no preview","typeGuard":"function groupExists(groups: { id: string }[], id: string): boolean {\n  return groups.some(g => g.id === id);\n}","tryCatchPattern":"try {\n  return renderDragPreview(items);\n} catch (err) {\n  if (String(err).includes('not found')) {\n    return null; // fallback: no custom preview\n  }\n  throw err;\n}","preventionTips":["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."],"tags":["ui","drag-and-drop","render","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"}