{"record":{"id":"21e5894196cea9e5","repo":"actualbudget/actual","slug":"template-cannot-be-null","errorCode":null,"errorMessage":"Template cannot be null","messagePattern":"Template cannot be null","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/desktop-client/src/components/budget/goals/reducer.ts","lineNumber":16,"sourceCode":"import {\n  addMonths,\n  dayFromDate,\n  firstDayOfMonth,\n  monthFromDate,\n} from '@actual-app/core/shared/months';\nimport type { Template } from '@actual-app/core/types/models/templates';\n\nimport type { Action } from './actions';\nimport type { DisplayTemplateType, ReducerState } from './constants';\n\nexport const DEFAULT_PRIORITY = 1;\n\nexport const getInitialState = (template: Template | null): ReducerState => {\n  if (!template) {\n    throw new Error('Template cannot be null');\n  }\n  const type = template.type;\n  switch (type) {\n    case 'simple':\n      return {\n        template: {\n          type: 'periodic',\n          amount: template.monthly ?? 0,\n          period: {\n            period: 'month',\n            amount: 1,\n          },\n          starting: firstDayOfMonth(new Date()),\n          priority: template.priority,\n          directive: template.directive,\n        },\n        displayType: 'fixed',\n      };","sourceCodeStart":1,"sourceCodeEnd":34,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/desktop-client/src/components/budget/goals/reducer.ts#L1-L34","documentation":"getInitialState builds ReducerState from a parsed Template and explicitly rejects null. Callers are expected to pass a template parsed from a category's notes; null means there is nothing to edit, so the function fails fast instead of returning partial state.","triggerScenarios":"Calling getInitialState(null), which happens when templateReducer receives a 'set-template' action with null payload, or mapTemplateTypesForUpdate builds a merged template that is null/undefined.","commonSituations":"Opening the template editor for a category with no template without checking first; a parsing step returning null on malformed notes and its result passed straight to the reducer; race where the template is cleared while the editor is initializing.","solutions":["Only call getInitialState when a template has been parsed and exists","Guard the dispatch site: check template != null before dispatching 'set-template'","Fix the upstream parser so valid template notes produce a Template object, not null"],"exampleFix":"// before\ndispatch({ type: 'set-template', payload: parsedTemplate });\n// after\nif (parsedTemplate) {\n  dispatch({ type: 'set-template', payload: parsedTemplate });\n}","handlingStrategy":"validation","validationCode":"if (template == null) {\n  return; // or show an empty-editor state instead of dispatching\n}\ndispatch({ type: 'set-template', payload: template });","typeGuard":"const hasTemplate = (t: Template | null | undefined): t is Template =>\n  t != null && typeof t.type === 'string';","tryCatchPattern":"try {\n  dispatch({ type: 'set-template', payload: template });\n} catch (e) {\n  if (e instanceof Error && e.message === 'Template cannot be null') {\n    notify('No template found on this category.');\n  } else throw e;\n}","preventionTips":["Only open the template editor when a parsed template exists","Check parser output for null before dispatching","Handle the no-template case in UI state instead of the reducer"],"tags":["null","budget-templates","client-side","validation"],"backgroundTag":"null-argument-guard","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}