'How to sum value in javascript array obj from specific search category to useState

I have js arr obj as below. My need is to sum value to useState base on category in the arr obj.

const [fit, setFit] = useState(0)
const [fat, setFat] = useState(0)
const [slim, setSlim] = useState(0)
const [test, setTest] = useState(0)

const arr = [
{id: '222', cost: 2, category: 'fit'},
{id: '333', cost: 3, category: 'fat'},
{id: '11', cost: 1, category: 'fat'},
{id: '11', cost: 0, category: 'fit'},
{id: '33', cost: 55, category: 'slim'},
{id: '55', cost: 33, category: 'slim'},
{id: '123', cost: 4, category: 'slim'}
]

How can i interate arr to get this effect ?

const [fit, setFit] = useState(2)
const [fat, setFat] = useState(4)
const [slim, setSlim] = useState(92)
const [test, setTest] = useState(0)

Thanks you for your help ;-)



Solution 1:[1]

You can create the function with reduce. Like this:

const arr = [
  { id: "222", cost: 2, category: "fit" },
  { id: "333", cost: 3, category: "fat" },
  { id: "11", cost: 1, category: "fat" },
  { id: "11", cost: 0, category: "fit" },
  { id: "33", cost: 55, category: "slim" },
  { id: "55", cost: 33, category: "slim" },
  { id: "123", cost: 4, category: "slim" }
];
const sumArrBy = (arr = [], category = "") =>
  arr.reduce(
    (prev, { cost, category: cat }) =>
      cat === category ? prev + cost : prev,
    0
  );

console.log(sumArrBy(arr, 'fit'))
console.log(sumArrBy(arr, 'fat'))
console.log(sumArrBy(arr, 'slim'))

Then you can set your state like this:

const [fit] = useState(sumArrBy(arr, "fit"));
const [fat] = useState(sumArrBy(arr, "fat"));
const [slim] = useState(sumArrBy(arr, "slim"));

Full code:

export default function App() {
  const arr = [
    { id: "222", cost: 2, category: "fit" },
    { id: "333", cost: 3, category: "fat" },
    { id: "11", cost: 1, category: "fat" },
    { id: "11", cost: 0, category: "fit" },
    { id: "33", cost: 55, category: "slim" },
    { id: "55", cost: 33, category: "slim" },
    { id: "123", cost: 4, category: "slim" }
  ];
  const sumArrBy = (arr = [], category = "") =>
    arr.reduce(
      (prev, { cost, category: cat }) =>
        cat === category ? prev + cost : prev,
      0
    );

  const [fit] = useState(sumArrBy(arr, "fit"));
  const [fat] = useState(sumArrBy(arr, "fat"));
  const [slim] = useState(sumArrBy(arr, "slim"));

  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <h2>Start editing to see some magic happen!</h2>
      <div>
        {fit}, {fat}, {slim}{" "}
      </div>
    </div>
  );
}

The working demo you can find here - codesandbox

EDIT: Other solutions for this issue have the "filter" function, which is not necessarily, because it makes another loop to filter values. In my case, I don't use the "filter". My solution is checking the category directly in the "reduce". It checks the category, and if the category is true, the function adds value cost to the results, if not, it skips and returns the previous correctly value.

Solution 2:[2]

I didn't like the other answers. This one is different in that it will create an object based on all the properties in your array, dynamically uses useEffect, and only iterates through your array once.

const arr = [
  {id: '222', cost: 2, category: 'fit'},
  {id: '333', cost: 3, category: 'fat'},
  {id: '11', cost: 1, category: 'fat'},
  {id: '11', cost: 0, category: 'fit'},
  {id: '33', cost: 55, category: 'slim'},
  {id: '55', cost: 33, category: 'slim'},
  {id: '123', cost: 4, category: 'slim'}
];

// reduce list to an object of total costs
const values = arr.reduce((a, b) => {
  if (!a[b.category]) a[b.category] = b.cost;
  else a[b.category] += b.cost;
  return a;
}, {});
console.log(values);

// create your values and setters in an object
const effects = {};
for (let key in values) {
  let [val, set] = useEffect(values[key]); // useEffect is not defined on StackOverflow
  effects[key] = {val, set};
}
/*
Creates obj with this structure:
{
  "fit": {
    "val": fit,
    "set": setFit,
  },
  ...
}
*/

Solution 3:[3]

Just filter and reduce:

const arr = [{id: '222', cost: 2, category: 'fit'},{id: '333', cost: 3, category: 'fat'},{id: '11', cost: 1, category: 'fat'},{id: '11', cost: 0, category: 'fit'},{id: '33', cost: 55, category: 'slim'},{id: '55', cost: 33, category: 'slim'},{id: '123', cost: 4, category: 'slim'}]

const sumBy = (arr, targetCat) => arr
    .filter(({ category }) => category === targetCat)
    .reduce((sum, { cost }) => sum + cost, 0);
    

console.log(sumBy(arr, 'fit'))
console.log(sumBy(arr, 'fat'))
console.log(sumBy(arr, 'slim'))
console.log(sumBy(arr, 'test'))
.as-console-wrapper{min-height: 100%!important; top: 0}

or you can create a reduced object once and receive amounts from it

const arr = [{id: '222', cost: 2, category: 'fit'},{id: '333', cost: 3, category: 'fat'},{id: '11', cost: 1, category: 'fat'},{id: '11', cost: 0, category: 'fit'},{id: '33', cost: 55, category: 'slim'},{id: '55', cost: 33, category: 'slim'},{id: '123', cost: 4, category: 'slim'}]

const reducedObj = arr.reduce((acc, { category, cost }) => ({ 
  ...acc, 
  [category]: acc[category] ? acc[category] + cost : cost 
}), {});

// reducedObj looks like:
// {
// "fit": 2,
// "fat": 4,
// "slim": 92  
// }
// there is no 'test' so we make a guard while get values

console.log(reducedObj['fit'] ?? 0)
console.log(reducedObj['fat'] ?? 0)
console.log(reducedObj['slim'] ?? 0)
console.log(reducedObj['test'] ?? 0)
.as-console-wrapper{min-height: 100%!important; top: 0}

Solution 4:[4]

You can find the total cost without iterating over another time using filter.

Using reduce, we can add the cost with respect to category

const arr = [
  { id: "222", cost: 2, category: "fit" },
  { id: "333", cost: 3, category: "fat" },
  { id: "11", cost: 1, category: "fat" },
  { id: "11", cost: 0, category: "fit" },
  { id: "33", cost: 55, category: "slim" },
  { id: "55", cost: 33, category: "slim" },
  { id: "123", cost: 4, category: "slim" }
];

const result = arr.reduce((acc, { category, cost }) => {
  !acc[category] ? (acc[category] = cost) : (acc[category] += cost);
  return acc;
}, {});

console.log(result);

Result

result =  { fit: 2, fat: 4, slim: 92 }

You can directly set that as a single state and access it as data.fit,data.fat, data.slim.

const [data,setData] = useState(result)

or

const [fit, setFit] = useState(result.fit)
const [fat, setFat] = useState(result.fat)
const [slim, setSlim] = useState(result.slim)

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2
Solution 3
Solution 4 Shan