Home > other >  How to add a property when building a new array of objects using Javascript .reduce, as opposed to .
How to add a property when building a new array of objects using Javascript .reduce, as opposed to .

Time:07-30

I have code that is successfully working using .filter().map() but I would like to rewrite it using .reduce() so as to use only one iteration, as opposed to the two in the former case.

Let's say we have an array of Fruit Objects...

var fruitArray = [
{name: 'Apple', color: 'red'}, 
{name: 'Banana', color: 'yellow'}, 
{name: 'Kiwi', color: 'green'}, 
{name: 'Blueberry', color: 'blue'}, 
{name: 'Orange', color: 'orange'}
]

And we want

  1. A new array that is a subset or filter of that set of fruit. (e.g. we can say we want "Fresh Fruit"... assume we have a function, isFresh, that determines this condition)

  2. We want each fruit in this new array to have a new property (or key-value pair) added to it, represented by foo: 'bar'.

I was able to do this in a fairly straightforward way with .filter() and .map() :

var freshFruit = fruitArray.filter(fruit => isFresh(fruit)).map((fruit) => {
      const value = 'bar'
      return {...fruit, foo: value }
    });

console.log(freshFruit);

/*
[
  { name: 'Apple', color: 'red', foo: 'bar' },
  { name: 'Kiwi', color: 'green', foo: 'bar' },
  { name: 'Blueberry', color: 'blue', foo: 'bar' }
]
*/

When I try to do this with reduce

var freshFruit = fruitArray.reduce((previousValue, fruit)=>{
        if (isFresh(fruit)) {
          const value = 'bar'
          fruit.foo = value
          previousValue.push(fruit);
        }
          return previousValue;
    }, []);

I get the desired output for freshFruit, but then I notice that it also edits my original fruitArray

console.log(fruitArray);

/*
[
  { name: 'Apple', color: 'red', foo: 'bar' },
  { name: 'Banana', color: 'yellow' },
  { name: 'Kiwi', color: 'green', foo: 'bar' },
  { name: 'Blueberry', color: 'blue', foo: 'bar' },
  { name: 'Orange', color: 'orange' }
]
*/

Notice how the reduce added the foo property.

...

Going through the process of typing this out, I may have solved my own problem.

I think I need to use the spread operator in the reduce function

var freshFruit = fruitArray.reduce((previousValue, fruit)=>{
    if (isFresh(fruit)) {
        const value = 'bar'
        previousValue.push({...fruit, foo: value});
    }
        return previousValue;
    }, []);

So this could still be a question with an answer, let me ask this

  1. Why is this spread syntax necessary? Why didn't my first attempt with the .reduce() method work? I can see now, in hindsight, why it shouldn't work, but I think I was stuck on the idea that reduce creates a temporary state/variable as opposed to live editing the existing variable.

  2. Are there material performance benefits from using .reduce() instead of .filter().map()? Should this be a general recommendation to anyone who is tempted to use a .filter().map()?

CodePudding user response:

Are there material performance benefits from using .reduce() instead of .filter().map()? Should this be a general recommendation to anyone who is tempted to use a .filter().map()?

I wouldn't worry too much about the overhead of iterating twice, as iteration is a very simple process. However, if the arrays are large, using .filter().map() means that an extra temporary array needs to be created to hold the result of .filter() before it can start mapping. This could be a significant amount of memory, and having to go back to the beginning for the .map() has poor cache locality.

However, in my opinion reduce() is usually harder to write and understand than filter() and map(). So unless performance is critical, I prefer to use .filter().map().

CodePudding user response:

As a side note, another "hacky-one-liner" way of mapping and filtering with .flatMap:

const fruitArray = [
    {name: 'Apple', color: 'red'}, 
    {name: 'Banana', color: 'yellow'}, 
    {name: 'Kiwi', color: 'green'}, 
    {name: 'Blueberry', color: 'blue'}, 
    {name: 'Orange', color: 'orange'},
];

const isFresh = ({ name }) => 
  ['Apple', 'Kiwi', 'Blueberry'].includes(name);
  
const result = fruitArray.flatMap((fruit) => 
  isFresh(fruit) ? { ...fruit, foo: 'bar' } : []);

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0 }

CodePudding user response:

Why didn't my first attempt with the .reduce() method work?

I just repeated your code and it works fine, check this example.

You log the original fruitArray array instead of the processed freshFruit array

const fruitArray = [
    {name: 'Apple', color: 'red'}, 
    {name: 'Banana', color: 'yellow'}, 
    {name: 'Kiwi', color: 'green'}, 
    {name: 'Blueberry', color: 'blue'}, 
    {name: 'Orange', color: 'orange'},
];

const isFresh = ({ name }) => 
  ['Apple', 'Kiwi', 'Blueberry'].includes(name);
  
const result = fruitArray.reduce((previousValue, fruit)=>{
    if (isFresh(fruit)) {
      const value = 'bar'
      fruit.foo = value
      previousValue.push(fruit);
    }
      return previousValue;
}, []);

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0 }

  • Related