![The ['1', '2', '3'].map(parseInt) Headache](/_next/image?url=https%3A%2F%2Fcdn.sanity.io%2Fimages%2F0sqjtkkp%2Fproduction%2Fbb705efcae157fbf85265c21963474f3f33558fa-2560x1707.jpg%3Frect%3D0%2C534%2C2560%2C640%26w%3D1024%26h%3D256&w=2048&q=75)
The ['1', '2', '3'].map(parseInt) Headache
The Issue
['10', '11', '12'].map(parseInt)
should result in [10, 11, 12]
. But it doesn't. Instead, it results in [10, NaN, 1]
. WHY?
Background
Recently, I was working on an app where the date data from the data source was coming in as a string (YYYY-MM-DD). Being the clever coder, I constructed a function to transform this string into a date...
1function parseDateString(d: string): Date {
2 //some error checking to be sure date is in YYYY-MM-DD format
3 const pattern = /\d{4}\-\d{2}\-\d{2}/;
4 if (!d || !d.match(pattern)) {
5 throw new Error('Could not parse date. Invalid or missing date string');
6 }
7
8 //here it is...
9 const [year, month, day] = d.split('-').map(parseInt);
10 //remember, the month is zero-indexed
11 return new Date(year, month-1, day);
12}
What a nice function, but I kept getting Invalid Date errors.
The Solution
I started unpacking the function and finally came up with this fix:
1function parseDateString(d: string): Date {
2 //some error checking to be sure date is in YYYY-MM-DD format
3 const pattern = /\d{4}\-\d{2}\-\d{2}/;
4 if (!d || !d.match(pattern)) {
5 throw new Error('Could not parse date. Invalid or missing date string');
6 }
7
8 //here it is...
9 const [year, month, day] = d.split('-').map(s => parseInt(s));
10 //remember, the month is zero-indexed
11 return new Date(year, month-1, day);
12}
The only change was in the .map()
function. Instead of passing parseInt
as the argument, I used an inline arrow function returning parseInt(s)
.
This worked, and I was ready to move on, but I didn't know why. The aha moment came when Visual Code popped up a hint...
1(method) Array<string>.map<number>(
2 callbackfn: (
3 value: string,
4 index: number,
5 array: string[]) => number,
6 thisArg?: any): number[]
7
8Calls a defined callback function on
9each element of an array,
10and returns an array that contains the results.
11
12@param callbackfn — A function that accepts
13up to three arguments.
The .map()
callback function "accepts up to three arguments," and the parseInt()
function can take two arguments: the value and, optionally, the base (radix). [read more on MDN] Thus, parseInt()
is getting the value and the index for each string it is to parse.
Explain!
What is happening is that .map(parseInt)
is passing the value to be parsed and also the value's index as the integer base for the returned value.
1['10', '11', '12'].map(parseInt);
2
3//same as ...
4['10', '11', '12'].map((value, index) => parseInt(value, index));
When '10' is parsed, the base is 0, which tells parseInt()
to figure out the base of the string value itself. In this case, it successfully returns 10.
When '11' is parsed, the base is 1, which automatically results in an error (NaN
). We (meaning Javascript) can't have a base-1 number system (base-2 is the smallest).
When '12' is parsed, the base is 2 and parseInt()
ignores the '2' as it is not a valid symbol in a base-2 system. The result is 1. Note: if we were to parse '11' with a base (radix) of 2, the result would be 3.
TLDR;
Essentially, parseInt()
gets messed up because .map(parseInt)
passes in the array item's index as the base (radix) when we want the values to be parsed as base-10 strings. By switching to .map(s => parseInt(s))
, we avoid this problem!!