195

I'm trying to use the isNaN global function inside an arrow function in a Node.js module but I'm getting this error:

[eslint] Unexpected use of 'isNaN'. (no-restricted-globals)

This is my code:

const isNumber = value => !isNaN(parseFloat(value));

module.exports = {
  isNumber,
};

Any idea on what am I doing wrong?

PS: I'm using the AirBnB style guide.

Elias Garcia
  • 6,772
  • 11
  • 34
  • 62

5 Answers5

360

As the documentation suggests, use Number.isNaN.

const isNumber = value => !Number.isNaN(Number(value));

Quoting Airbnb's documentation:

Why? The global isNaN coerces non-numbers to numbers, returning true for anything that coerces to NaN. If this behavior is desired, make it explicit.

// bad
isNaN('1.2'); // false
isNaN('1.2.3'); // true

// good
Number.isNaN('1.2.3'); // false
Number.isNaN(Number('1.2.3')); // true
Andy Gaskell
  • 31,495
  • 6
  • 74
  • 83
  • 44
    But `isNaN` and `Number.isNaN` are not the same functions. For instance ```isNaN('1a') true Number.isNaN('1a') false``` – rosencreuz Apr 19 '18 at 09:41
  • 3
    @rosencreuz More like undesired behavior. That's why there is `Number('1.2.3')` in the example above. – Patrick Portal Nov 30 '18 at 15:12
  • 6
    What a stupid rule, the whole point is to coerce to a number or you might as well just do a `typeof` check. – Dominic Dec 18 '18 at 18:08
  • 1
    I use `Number.isNaN(+'1.2.3')` which is just an extra `+` if you use `Number.isNaN` – Ibraheem May 21 '20 at 16:40
  • I hope they will remove this, isNaN and Number.isNaN have different behaviors. – Or Assayag Oct 05 '21 at 10:55
  • 1
    I agree it's stupid. One checks if the input is numeric, the other checks if it's the literal `NaN`. The only reason some find it confusing is because they're named similar things. – Electric Coffee Jul 07 '22 at 09:12
  • boolean / empty string or string with space / null values are detected as number by this function `const isNumber = value => !Number.isNaN(Number(value));` – vincent baronnet May 04 '23 at 14:45
18

FYI, this will not work for IE. Check here at browser compatibility.

thyforhtian
  • 366
  • 3
  • 9
9

Coercing unknown type to number lead to unexpected results in javascript, the eslint rules prevent you to coerce with isNaN that should be used only to check if a number is equal to the specific NaN value.. this is javascript. The following function checks prevent you to coerce non-numeric value to a number.

/**
 * Return true if the value can be evaluated as a Number.
 * @param {*} value
 * @param {boolean} finite - If true, treat non-finite numbers as invalid.
 */
const isNumeric = (value, finite = false) =>
  (typeof value === 'number' ||
    (typeof value === 'string' && value.trim() !== '')) &&
  (finite ? Number.isFinite(Number(value)) : !Number.isNaN(Number(value)));

Test case, you can switch isNumeric with isNumeric(v) => typeof isNaN(v) === 'number' to see what cases are failing coercing directly any value (using Number() or +string have the same issues)

describe('isNumeric', () => {
  it('evaluate "1" as numeric', () => {
    expect(isNumeric('1')).to.equal(true);
    expect(isNumeric('1', true)).to.equal(true);
  });
  it('evaluate "1.1" as numeric', () => {
    expect(isNumeric('1.1')).to.equal(true);
    expect(isNumeric('1.1', true)).to.equal(true);
  });
  it('evaluate "100.145864" as numeric', () => {
    expect(isNumeric('100.145864')).to.equal(true);
    expect(isNumeric('100.145864', true)).to.equal(true);
  });
  it('evaluate 1 as numeric', () => {
    expect(isNumeric(1)).to.equal(true);
    expect(isNumeric(1, true)).to.equal(true);
  });
  it('evaluate 1.1 as numeric', () => {
    expect(isNumeric(1.1)).to.equal(true);
    expect(isNumeric(1.1, true)).to.equal(true);
  });
  it('evaluate 100.145864 as numeric', () => {
    expect(isNumeric(100.145864)).to.equal(true);
    expect(isNumeric(100.145864, true)).to.equal(true);
  });
  it('evaluate "one" as non-numeric', () => {
    expect(isNumeric('one')).to.equal(false);
    expect(isNumeric('one', true)).to.equal(false);
  });
  it('evaluate undefined as non-numeric', () => {
    expect(isNumeric(undefined)).to.equal(false);
    expect(isNumeric(undefined, true)).to.equal(false);
  });
  it('evaluate null as non-numeric', () => {
    expect(isNumeric(null)).to.equal(false);
    expect(isNumeric(null, true)).to.equal(false);
  });
  it('evaluate NaN as non-numeric', () => {
    expect(isNumeric(NaN)).to.equal(false);
    expect(isNumeric(NaN, true)).to.equal(false);
  });
  it('evaluate true as non-numeric', () => {
    expect(isNumeric(true)).to.equal(false);
    expect(isNumeric(true, true)).to.equal(false);
  });
  it('evaluate false as non-numeric', () => {
    expect(isNumeric(false)).to.equal(false);
    expect(isNumeric(false, true)).to.equal(false);
  });
  it('evaluate an empty string as non-numeric', () => {
    expect(isNumeric('')).to.equal(false);
    expect(isNumeric('', true)).to.equal(false);
  });
  it('evaluate "100,2" as non-numeric', () => {
    expect(isNumeric('100,2')).to.equal(false);
    expect(isNumeric('100,2', true)).to.equal(false);
  });
  it('evaluate "1.1.1" as non-numeric', () => {
    expect(isNumeric('1.1.1')).to.equal(false);
    expect(isNumeric('1.1.1', true)).to.equal(false);
  });
  it('evaluate "123123 + 123123" as non-numeric', () => {
    expect(isNumeric('123123 + 123123')).to.equal(false);
    expect(isNumeric('123123 + 123123', true)).to.equal(false);
  });
  it('evaluate "123123 - 123123" as non-numeric', () => {
    expect(isNumeric('123123 - 123123')).to.equal(false);
    expect(isNumeric('123123 - 123123', true)).to.equal(false);
  });
  it('evaluate "123123 / 123123" as non-numeric', () => {
    expect(isNumeric('123123 / 123123')).to.equal(false);
    expect(isNumeric('123123 / 123123', true)).to.equal(false);
  });
  it('evaluate "123123 * 123123" as non-numeric', () => {
    expect(isNumeric('123123 * 123123')).to.equal(false);
    expect(isNumeric('123123 * 123123', true)).to.equal(false);
  });
  it('evaluate "  " as non-numeric', () => {
    expect(isNumeric('  ')).to.equal(false);
    expect(isNumeric('  ', true)).to.equal(false);
  });
  it('evaluate " " as non-numeric', () => {
    expect(isNumeric(' ')).to.equal(false);
    expect(isNumeric(' ', true)).to.equal(false);
  });
  it('evaluate Infinity as numeric', () => {
    expect(isNumeric(Infinity)).to.equal(true);
  });
  it('evaluate -Infinity as numeric', () => {
    expect(isNumeric(-Infinity)).to.equal(true);
  });
  it('evaluate Infinity as non-numeric with finite=true', () => {
    expect(isNumeric(Infinity, true)).to.equal(false);
  });
  it('evaluate -Infinity as non-numeric with finite=true', () => {
    expect(isNumeric(-Infinity, true)).to.equal(false);
  });
  it('evaluate Number.MAX_SAFE_INTEGER as numeric', () => {
    expect(isNumeric(Number.MAX_SAFE_INTEGER)).to.equal(true);
    expect(isNumeric(Number.MAX_SAFE_INTEGER, true)).to.equal(true);
  });
  it('evaluate Number.MIN_SAFE_INTEGER as numeric ', () => {
    expect(isNumeric(Number.MIN_SAFE_INTEGER)).to.equal(true);
    expect(isNumeric(Number.MIN_SAFE_INTEGER, true)).to.equal(true);
  });
  it('evaluate "1e3" as numeric ', () => {
    expect(isNumeric('1e3')).to.equal(true);
    expect(isNumeric('1e3', true)).to.equal(true);
  });
  it('evaluate "1e.3" as non-numeric ', () => {
    expect(isNumeric('1e.3')).to.equal(false);
    expect(isNumeric('1e.3', true)).to.equal(false);
  });
  it('evaluate "1e3.3" as non-numeric ', () => {
    expect(isNumeric('1e1.3')).to.equal(false);
    expect(isNumeric('1e1.3', true)).to.equal(false);
  });
  it('evaluate "1e" as non-numeric ', () => {
    expect(isNumeric('1e')).to.equal(false);
    expect(isNumeric('1e', true)).to.equal(false);
  });
  it('evaluate "1e0" as numeric ', () => {
    expect(isNumeric('1e0')).to.equal(true);
    expect(isNumeric('1e0', true)).to.equal(true);
  });
  it('evaluate "-1e3" as numeric ', () => {
    expect(isNumeric('-1e3')).to.equal(true);
    expect(isNumeric('-1e3', true)).to.equal(true);
  });
  it('evaluate an object as non-numeric ', () => {
    expect(isNumeric({})).to.equal(false);
    expect(isNumeric({}, true)).to.equal(false);
  });
});

you can also disable the rule in the overrides section of the eslint configuration file if you have a legitimate use case for it (or use directly Number(value) or +value to coerce without triggering the rule.

Trivia (alternative version Using REGEXP as proposed by @Noby Fujioka answer, updated to pass the proposed test cases

const isNumeric = (value, finite = false) => (typeof value === 'number' || (typeof value === 'string' && /^(([\-]?[0-9]+)([\.e][0-9]+)?)$/.test(value))) && (finite ? Number.isFinite(Number(value)) : !Number.isNaN(value));

isNaN behavior from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isNaN

isNaN("NaN"); // true
isNaN(undefined); // true
isNaN({}); // true
isNaN("blabla"); // true
isNaN(true); // false, this is coerced to 1
isNaN(null); // false, this is coerced to 0
isNaN("37"); // false, this is coerced to 37
isNaN("37.37"); // false, this is coerced to 37.37
isNaN(""); // false, this is coerced to 0
isNaN(" "); // false, this is coerced to 0
2

In my case, I wanted to treat 5 (integer), 5.4(decimal), '5', '5.4' as numbers but nothing else for example.

If you have the similar requirements, below may work better:

const isNum = num => /^\d+$/.test(num) || /^\d+\.\d+$/.test(num);

//Check your variable if it is a number.
let myNum = 5;
console.log(isNum(myNum))

To include negative numbers:

const isNum = num => /^-?\d+$/.test(num) || /^-?\d+\.\d+$/.test(num);

This will remove your issue of global use of isNaN as well. If you convert the isNum function to a normal ES5 function, it will work on IE browser as well.

Noby Fujioka
  • 1,744
  • 1
  • 12
  • 15
-1

For me this worked fine and didn't have any problem with ESlint

window.isNaN()

Yoannes Geissler
  • 791
  • 1
  • 9
  • 18
  • Using AirBnb standard you should get: ESLint: 'window.isNaN' is restricted from being used. Please use Number.isNaN instead(no-restricted-properties) – Bartek Maciejewski Feb 24 '20 at 09:35
  • @BartekMaciejewski The problem with Number.isNaN is that they are not same functions. For example. `Number.isNaN('abc')` is `false`. And `isNaN('abc')` is `true` – Yoannes Geissler Feb 24 '20 at 11:57
  • Yes, I totally agree with you - just wanted to mention that using `window.isNan()` is against other AirBnb's config (the rule is https://eslint.org/docs/rules/no-restricted-properties) – Bartek Maciejewski Feb 24 '20 at 12:36
  • 1
    @BartekMaciejewski This is a pain, I unfortunately had to deactivate the `no-restricted-properties` because of this issue – Yoannes Geissler Feb 24 '20 at 13:14