Tricky use case of Array.prototype.map in JS
- Published on
- • 4 mins read•––– views
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:
// SyntaxparseInt(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') // => 10parseInt('10', 10) // => 10parseInt('10', 2) // => 2parseInt('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!