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