{"record":{"id":"5964bf6aa8f31206","repo":"actualbudget/actual","slug":"value-is-not-a-number-typeof-localvalue-lo","errorCode":null,"errorMessage":"Value is not a number (${typeof localValue}): ${localValue}","messagePattern":"Value is not a number \\((.+?)\\): (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/desktop-client/src/hooks/useFormat.ts","lineNumber":91,"sourceCode":"        // 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      };\n    }\n    default:\n      throw new Error('Unknown format type: ' + type);\n  }\n}\n","sourceCodeStart":73,"sourceCodeEnd":109,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/desktop-client/src/hooks/useFormat.ts#L73-L109","documentation":"After the string-coercion branch, format re-checks that the value is a number before calling integerToCurrency. Values that are neither null/empty-string nor string nor number (e.g. booleans, objects, arrays, undefined after non-empty check) fall through to this check and throw 'Value is not a number (<typeof>): <value>'.","triggerScenarios":"Passing true/false, an object like {amount: 123}, an array, NaN is a number so it passes this check but not undefined or Symbol to a 'financial' format type; also values where null/'' normalization was skipped because the value was undefined (undefined !== null? actually null==null caught, but undefined === '' false, undefined == null true — so mainly objects/booleans).","commonSituations":"A query aggregation returning {value: n} objects instead of raw numbers; boolean flags accidentally bound into amount fields; refactors changing IntegerAmount to a wrapped object.","solutions":["Extract the numeric field from the object before formatting (format(obj.amount, 'financial'))","Coerce explicitly with Number(value) and validate isFinite before the call","Fix the type at the source so the value is an IntegerAmount (number)","Add a runtime guard at the call site that falls back to 0 for non-numeric values"],"exampleFix":"// before\nformat({ amount: 12300 }, 'financial'); // object -> throws\n// after\nformat(12300, 'financial'); // pass IntegerAmount number","handlingStrategy":"type-guard","validationCode":"if (value != null && typeof value !== 'string' && typeof value !== 'number') {\n  throw new TypeError(`financial format needs number|string|null, got ${typeof value}`);\n}","typeGuard":"function isFormatableFinancialValue(v: unknown): v is number | string | null | undefined | '' {\n  return v == null || v === '' || typeof v === 'string' || typeof v === 'number';\n}","tryCatchPattern":"let out: string;\ntry {\n  out = format(value, 'financial');\n} catch (e) {\n  if (e instanceof Error && e.message.startsWith('Value is not a number')) {\n    out = format(0, 'financial');\n  } else throw e;\n}","preventionTips":["Unwrap aggregated query objects ({amount: n}) before formatting","Type component props as IntegerAmount | null so booleans/objects can't flow in","Validate data shapes at the API/AQL boundary with zod or manual guards","Fall back to 0 for null/undefined amounts instead of passing raw values"],"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"}