{"record":{"id":"5cebb53d0a53be42","repo":"actualbudget/actual","slug":"invalid-numeric-value-localvalue","errorCode":null,"errorMessage":"Invalid numeric value: ${localValue}","messagePattern":"Invalid numeric value: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/desktop-client/src/hooks/useFormat.ts","lineNumber":85,"sourceCode":"    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.\n        // Ideally the string case will be removed in the future. We should always\n        // use the IntegerAmount.\n        // The parseInt with the replace is a workaround for the case and looks like\n        // the \"least wrong\" solution.\n        const integerString = localValue.replace(/[^\\d-]/g, '');\n        const parsed = parseInt(integerString, 10);\n        if (isNaN(parsed)) {\n          throw new Error(`Invalid numeric value: ${localValue}`);\n        }\n        localValue = parsed;\n      }\n\n      if (typeof localValue !== 'number') {\n        throw new Error(\n          'Value is not a number (' + typeof localValue + '): ' + localValue,\n        );\n      }\n\n      return {\n        numericValue: localValue,\n        formattedString: integerToCurrency(\n          localValue,\n          formatter,\n          decimalPlaces,\n        ),\n      };","sourceCodeStart":67,"sourceCodeEnd":103,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/desktop-client/src/hooks/useFormat.ts#L67-L103","documentation":"For 'financial' format types, string values are accepted for backwards compatibility but are strictly cleaned (strip everything except digits and minus) and parsed with parseInt. If nothing numeric remains (isNaN), format throws `Invalid numeric value: ${localValue}`. This rejects strings that contain no recoverable integer amount.","triggerScenarios":"Passing a non-numeric string like 'abc', '12,3.45abc' that strips to '12345' is fine, but '', 'N/A', '-' alone, or '1.2.3' variants that strip to nothing hit this throw. Also currency symbols only, or boolean true (stringified path) with no digits.","commonSituations":"Legacy components sending raw user input (e.g. '12.00' vs '12300' integer amounts) where the input was cleared or contained letters; spreadsheet cells containing text; import/API data with placeholder strings like '—' or 'n/a'.","solutions":["Convert legacy string amounts to IntegerAmount numbers at the data boundary instead of relying on the string path","Pre-parse with a dedicated parser (currencyToAmount / amountToInteger from shared/util) and handle failure before formatting","Sanitize the string: strip non-numeric chars yourself and check the result is non-empty before calling","Show an edit state to the user instead of formatting when the value can't be parsed"],"exampleFix":"// before\nformat('N/A', 'financial'); // throws\n// after\nconst parsed = currencyToAmount('N/A');\nformat(parsed == null || Number.isNaN(parsed) ? 0 : parsed, 'financial');","handlingStrategy":"validation","validationCode":"const cleaned = typeof v === 'string' ? v.replace(/[^\\d-]/g, '') : '';\nif (typeof v === 'string' && (cleaned === '' || Number.isNaN(parseInt(cleaned, 10)))) {\n  return 0; // or reject before formatting\n}","typeGuard":"function isParsableAmountString(v: unknown): v is string {\n  return typeof v === 'string' && v.replace(/[^\\d-]/g, '') !== ''\n    && !Number.isNaN(parseInt(v.replace(/[^\\d-]/g, ''), 10));\n}","tryCatchPattern":"let out: string;\ntry {\n  out = format(value, 'financial');\n} catch (e) {\n  if (e instanceof Error && e.message.startsWith('Invalid numeric value')) {\n    out = format(0, 'financial');\n  } else throw e;\n}","preventionTips":["Stop sending string amounts; always use IntegerAmount numbers (amountToInteger)","Parse user input with currencyToAmount/fromEdit at the edit boundary, not at format time","Treat 'N/A'/'—' placeholders as null/0 before they reach formatters","Cover the string-coercion path with tests for empty and non-numeric strings"],"tags":["formatting","parsing","legacy"],"backgroundTag":"invalid-numeric-input","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}