Tricky use case of Array.prototype.map in JS

- Published on
- /4 mins read/---
If you are familiar with functional programming, Array.prototype.map must be a function that you work with every day.
For me, map() is a powerful function, but there might be some tricky use case of it that you don't know about!
Let's walk through some basic knowledge
The map() method creates a new array from the calling array by applying a provided callback function once for each element of the calling array
Simple use cases
Giving this array
let devs = [
{ id: '1', name: 'Leo', yob: 1995 },
{ id: '2', name: 'Paul', yob: 1995 },
{ id: '3', name: 'Jesse', yob: 1996 },
{ id: '4', name: 'Ken', yob: 1997 },
]What can we do using map() function:
- Getting new values from array
let ages = devs.map((dev) => {
let currentYear = new Date().getFullYear()
return currentYear - dev.yob
})
console.log(ages) // => [25, 25, 24, 23]- Extracting values from array of objects
let names = devs.map((dev) => dev.name)
console.log(names) // => ["Leo", "Paul", "Jesse", "Ken"]- Rendering list in React application
import React from 'react'
export default DeveloperProfiles = ({ devs }) => {
return (
<ul>
{devs.map((dev) => (
<li key={dev.id}>{dev.name}</li>
))}
</ul>
)
}Tricky use case
It is common to pass the pre-defined callback function as map() argument like this:
let extractId = (dev) => {
return dev.id
}
let ids = devs.map(extractId)
console.log(ids) // => ["1", "2", "3", "4"]But this habit may lead to unexpected behaviors with functions that take additional arguments.
Consider this case - we need to get all ids as numbers:
// ids = ["1", "2", "3", "4"]
let idsInNumber = ids.map(parseInt)
console.log(idsInNumber) // => ???You might expect the result is [1, 2, 3, 4], but the actual result is [1, NaN, NaN, NaN]
We encountered this problem at Cốc Cốc recently, it took me a while to figure out, and here's the answer...
Usually, we use map() callback with 1 argument (the element being traversed), but Array.prototype.map passes 3 arguments:
- the element
- the index
- the calling array (which we don't use most of the time)
And the callback in this case - parseInt is often used with 1 argument but it takes 2:
// Syntax
parseInt(string [, radix])string: the value to parseradix: [Optional]: an integer that represent the radix (the base in mathematical numeral systems) - usually, it's 10
For example:
parseInt('10') // => 10
parseInt('10', 10) // => 10
parseInt('10', 2) // => 2
parseInt('10', 4) // => 4And here you can see the source of confusion !
The third argument of map() is ignored by parseInt - but not the second one!
Hence, the iteration steps of map() look like this:
// map(parseInt) => map(parseInt(value, index))
/* index is 0 */ parseInt('1', 0) // => 1
/* index is 1 */ parseInt('2', 1) // => NaN
/* index is 2 */ parseInt('3', 2) // => NaN
/* index is 3 */ parseInt('4', 3) // => NaNSolution
As you've known the source of the problem, the solution is not to pass all of the map()'s arguments to your callback if you're not sure how it works
- Passing only the arguments that your callback needs
function returnInt(element) {
return parseInt(element, 10)
}
;['1', '2', '3', '4'].map(returnInt)
// => [1, 2, 3, 4]
// Same as above, but using the concise arrow function syntax
// Better use this if you don't need to re-use the callback
;['1', '2', '3', '4'].map((numb) => parseInt(numb, 10))
// => [1, 2, 3, 4]- A simpler way to achieve our case like Airbnb style guide
;['1', '2', '3', '4'].map(Number)
// => [1, 2, 3, 4]And that's what I know about Array.prototype.map function, feel free to share your use cases in the comment section
Happy coding!
