Ilja
Ilja

Reputation: 46527

Type 'null' must have a '[Symbol.iterator]()' method

I have a function below that takes in numeric value and parses it into currency values like 11k, 1m etc... It achieves the task, but I am receiving ts error on this line:

const [match] = `${value / divider}`.match(regx);

Error

Type 'null' must have a 'Symbol.iterator' method that returns an iterator.ts(2488)

Full code

export function getCurrencyValue(value: number) {
  const ranges = [{ divider: 1e6, suffix: 'm' }, { divider: 1e3, suffix: 'k' }];
  const regx = new RegExp('^-?\\d+(?:.\\d{0,1})?');
  let formatedValue = '';

  ranges.forEach(range => {
    const { divider, suffix } = range;

    if (value >= divider) {
      const [match] = `${value / divider}`.match(regx);

      if (match) {
        formatedValue = `${match}${suffix}`;
      }
    }
  });

  return formatedValue || value;
}

Upvotes: 1

Views: 6296

Answers (1)

T.J. Crowder
T.J. Crowder

Reputation: 1075337

You can't destructure null, so

const [match] = `${value / divider}`.match(regx);

...would throw if there were no match, since match returns null when there's no match. You'll need to split that into two statements, or use the || [] trick or similar. Looking at your full code, it doesn't seem like destructuring helps here (because of the possible null), simply:

const match = `${value / divider}`.match(regx);

if (match) {
  formatedValue = `${match[0]}${suffix}`;
}

The reason the error message says what it says is that the kind of destructuring you're doing (using []) relies on getting an iterator from the thing you're destructuring (the result of match). You get an iterator from something by using its Symbol.iterator method:

const it = `${value / divider}`.match(regx)[Symbol.iterator]();

Since one of the things match can return is null, and null doesn't (and can't) have a Symbol.iterator property, TypeScript complains about it.


Side note: forEach doesn't seem like the right tool here, since you want to stop the first time you get a match. Right now, you'll write to formattedValue for the 1e6 range, but then overwrite it with the result of formatting the 1e3 range.

for-of would be a good choice:

export function getCurrencyValue(value: number) {
  const ranges = [{ divider: 1e6, suffix: 'm' }, { divider: 1e3, suffix: 'k' }];
  const regx = new RegExp('^-?\\d+(?:.\\d{0,1})?');

  for (const { divider, suffix } of ranges) {
    if (value >= divider) {
      const match = `${value / divider}`.match(regx);
      if (match) {
        return `${match[0]}${suffix}`;
      }
    }
  });

  return String(value); // Presumably you always want it to be a string
}

I'd also probably use literal regular expression syntax for clarity (don't have to double-escape backslashes), and move both the ranges and regx outside the function so they aren't re-created every time (since this is in a module, and separately since the regex doesn't have the g or y flags):

const ranges = [{ divider: 1e6, suffix: 'm' }, { divider: 1e3, suffix: 'k' }];
const regx = /^-?\d+(?:.\d{0,1})?/;
export function getCurrencyValue(value: number) {

  for (const { divider, suffix } of ranges) {
    if (value >= divider) {
      const match = `${value / divider}`.match(regx);
      if (match) {
        return `${match[0]}${suffix}`;
      }
    }
  });

  return String(value); // Presumably you always want it to be a string
}

You've said you're not allowed to use for-of in your environment because you're transpiling to ES5 and not allowed to rely on regenerator-runtime. So instead of forEach, you'd want some:

const ranges = [{ divider: 1e6, suffix: 'm' }, { divider: 1e3, suffix: 'k' }];
const regx = /^-?\d+(?:.\d{0,1})?/;
export function getCurrencyValue(value: number) {
  let formattedValue = String(value); // Presumably you want it to always be a string

  // Not allowed to use for-of, and the rule saying not to use it also doesn't like `for` loops, so...
  ranges.some(({ divider, suffix }) => {
    if (value >= divider) {
      const match = `${value / divider}`.match(regx);
      if (match) {
        formattedValue = `${match[0]}${suffix}`;
        return true;
      }
    }
    return false;
  });

  return formattedValue;
}

Or with reduce:

const ranges = [{ divider: 1e6, suffix: 'm' }, { divider: 1e3, suffix: 'k' }];
const regx = /^-?\d+(?:.\d{0,1})?/;
export function getCurrencyValue(value: number) {

  // Not allowed to use for-of, and the rule saying not to use it also doesn't like `for` loops, so...
  return ranges.reduce((formattedValue, { divider, suffix }) => {
    if (value >= divider) {
      const match = `${value / divider}`.match(regx);
      if (match) {
        formattedValue = `${match[0]}${suffix}`;
      }
    }
    return formattedValue;
  }, String(value)); // Presumably you always want it to be a string
}

Upvotes: 8

Related Questions