{"record":{"id":"b97720accca9b37f","repo":"actualbudget/actual","slug":"value-is-not-a-number-typeof-value-value","errorCode":null,"errorMessage":"Value is not a number (${typeof value}): ${value}","messagePattern":"Value is not a number \\((.+?)\\): (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/desktop-client/src/hooks/useFormat.ts","lineNumber":59,"sourceCode":"\nfunction format(\n  value: unknown,\n  type: FormatType,\n  formatter: { format: (value: number) => string },\n  decimalPlaces: number,\n): FormatResult {\n  switch (type) {\n    case 'string': {\n      const val = JSON.stringify(value);\n\n      if (val.charAt(0) === '\"' && val.charAt(val.length - 1) === '\"') {\n        return { formattedString: val.slice(1, -1) };\n      }\n      return { formattedString: val };\n    }\n    case 'number':\n      if (typeof value !== 'number') {\n        throw new Error(\n          'Value is not a number (' + typeof value + '): ' + value,\n        );\n      }\n      return { numericValue: value, formattedString: formatter.format(value) };\n    case 'percentage':\n      return { formattedString: value + '%' };\n    case 'financial-with-sign':\n    case 'financial-no-decimals':\n    case 'financial': {\n      let localValue = value;\n      if (localValue == null || localValue === '') {\n        localValue = 0;\n      } else if (typeof localValue === 'string') {\n        // This case is generally flawed, but we need to support it for\n        // backwards compatibility for now.\n        // For example, it is not clear how the string might look like\n        // The Budget sends 12300, if the user inputs 123.00, but\n        // there might be other components that send 123 with the same user input.","sourceCodeStart":41,"sourceCodeEnd":77,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/desktop-client/src/hooks/useFormat.ts#L41-L77","documentation":"The format function's 'number' case requires the raw value to be a JS number so Intl-based formatter.format(value) can run. If value is a string, null, undefined, object, etc., it throws 'Value is not a number (<typeof>): <value>'. Note 'financial' types coerce strings, but 'number' deliberately does not.","triggerScenarios":"Calling format(value, 'number') with a string like '123' (common after fetching data that wasn't coerced), null/undefined, NaN-adjacent values from a spreadsheet cell, or an object returned by a query aggregation.","commonSituations":"Binding a report/CSV column configured as 'number' to data that arrives as strings from an API or AQL query; passing state from an uncontrolled input (always a string) directly to the formatter; a refactor changing the data type upstream.","solutions":["Coerce the value before formatting: Number(value) after checking it's not NaN","Pass the 'financial' type instead if the value may be an integer-amount string and currency formatting is acceptable","Fix the data source (query select / API mapping) to return numbers, not strings","Guard the call site: typeof v === 'number' ? format(v, 'number') : format(0, 'number')"],"exampleFix":"// before\nformat(row.total as unknown, 'number'); // row.total is '123' (string)\n// after\nconst n = Number(row.total);\nformat(Number.isFinite(n) ? n : 0, 'number');","handlingStrategy":"type-guard","validationCode":"if (typeof value !== 'number' || !Number.isFinite(value)) {\n  throw new TypeError(`format('number') requires a finite number, got ${typeof value}`);\n}","typeGuard":"function isNumeric(v: unknown): v is number {\n  return typeof v === 'number' && Number.isFinite(v);\n}","tryCatchPattern":"let out: string;\ntry {\n  out = format(value, 'number');\n} catch (e) {\n  if (e instanceof Error && e.message.startsWith('Value is not a number')) {\n    out = format(0, 'number');\n  } else throw e;\n}","preventionTips":["Coerce API/query results to numbers at the data layer, not at render time","Use the FormatType union, not raw strings, so mismatches surface in review","Remember 'number' does not coerce strings — only 'financial' does (and only legacy)","Add a lint/test that formatted fields come from typed numeric columns"],"tags":["formatting","type-error","validation"],"backgroundTag":"type-mismatch-argument","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}