Replace deep child object, while keeping state immutable in React - javascript

I'm new to react and redux. I have an object is an array of child objects, which contain arrays
const initialState = {
sum: 0,
denomGroups: [
{
coins: [
{ name: 'Penny', namePlural: 'Pennies', label: '1¢', value: .01, sum: 0 },
{ name: 'Nickel', namePlural: 'Nickels', label: '5¢', value: .05, sum: 0 },
{ name: 'Dime', namePlural: 'Dimes', label: '10¢', value: .10, sum: 0 },
{ name: 'Quarter', namePlural: 'Quarters', label: '25¢', value: .25, sum: 0 }
]
},
{
bills: [
{ name: 'Dollar', namePlural: 'Dollars', label: '$1', value: 1, sum: 0 },
{ name: 'Five', namePlural: 'Fives', label: '$5', value: 5, sum: 0 },
{ name: 'Ten', namePlural: 'Tens', label: '$10', value: 10, sum: 0 },
{ name: 'Twenty', namePlural: 'Twentys', label: '$20', value: 20, sum: 0 },
{ name: 'Fifty', namePlural: 'Fiftys', label: '$50', value: 50, sum: 0 },
{ name: 'Hundred', namePlural: 'Hundreds', label: '$100', value: 100, sum: 0 }
]
}
]
};
I have an action that gets passed a value and the name of the denomination
export function increaseSum(value, denom) {
return { type: types.ADD_TO_SUM, value: value, denom: denom }
}
Inside of my reducer I wrote a helper class to identify where this denomination is within the object:
function findDenomination(denoms, action) {
let denomMap = {},
currentDenom = {};
for (let i = 0; denoms.length >= i + 1; i++) {
let denomGroup = denoms[Object.keys(denoms)[i]];
for (var key in denomGroup) {
if (!currentDenom.length) {
currentDenom = denomGroup[key].filter(x => x.name === action.denom);
if (currentDenom.length > 0) {
denomMap.group = i;
denomMap.key = key;
denomMap.index = denomGroup[key].findIndex(x => x.name === action.denom);
}
}
}
if (currentDenom.length > 0) {
break;
}
}
return denomMap;
}
And within the reducer itself I am using Object.assign to make a deep copy of the denomGroups, in a way that I would think keeps it immutable.
function countableItems(state = initialState, action) {
switch (action.type) {
case types.ADD_TO_SUM:
let denomMap = findDenomination(state.denomGroups, action);
state.denomGroups = Object.assign({}, state.denomGroups, state.denomGroups[denomMap.group][denomMap.key][denomMap.index].sum = parseFloat(action.value));
return state;
default:
return state;
}
}
Is it clear to anyone why this is getting flagged with the error: A state mutation was detected inside a dispatch

You attempted to mutate an immutable. Hence the error.
The whole point of immutables is that an immutable should never ever change anywhere inside of it after it's create. That's why all functions that change it create a new instance of an immutable. If you try to change something deep inside an immutable, that's still a mutation, and thus bad.
This ensures you can just check the objects themselves for equality instead of having to do a deep check, as well as ensuring data integrity.
What you should be doing is just mutating it (if you're using the immutables library, you can use setIn), which will create a new Map. You can call that just on state.
Something like this:
case types.ADD_TO_SUM:
const denomMap = findDenomination(state.denomGroups, action);
return state.setIn(['denomGroup', denomGroup.key, denomGroup.index, sum], parseFloat(action.value));

Related

How to check if Javascript array of objects has the same value throughout

I have the following array:
distributors = [{ Name: 'Foo', Count: 0}, { Name: 'Bar', Count: 0}, { Name: 'Baz', Count: 0}]
How do I check if the 'Count' is 0 for every object in the array? Ideally, I would like to write a function to return boolean if the value for 'Count' is 0 for every object in the array.
You can use every method. Every method checks if all elements in an array pass a test and returns true or false based on the function you have provided. So, If every element pass the test it returns true otherwise false.
const distributors = [
{ Name: 'Foo', Count: 0 },
{ Name: 'Bar', Count: 0 },
{ Name: 'Baz', Count: 0 },
];
const ret = distributors.every((x) => x.Count === 0);
console.log(ret);
This function returns true if all the Count property is 0
function checkCount(distributors) {
for (let element of distributors) {
if (element.Count) {
return false;
}
}
return true;
}

Comparing array elements against the rest of the array

The question might be a bit vague, but I'll explain the result I'm expecting to get with an example.
Say I have the following array made out of objects with the following shape:
[
{
id: 1,
value: 10
},
{
id: 2,
value: 100
},
{
id: 3,
value: 10
},
{
id: 4,
value: 10
},
{
id: 5,
value: 1000
},
]
This array might contain hundrends, maybe thousands of entries, but for simplicity, I'll keep it small.
What I'm trying to achieve is compare the value property of every object with the other value properties and assign a new property duplicate with a boolean value to that specific object.
Given the example above, I would expect to receive an array with the following members:
[
{
id: 1,
value: 10,
duplicate: true
},
{
id: 2,
value: 100
},
{
id: 3,
value: 10,
duplicate: true
},
{
id: 4,
value: 10,
duplicate: true
},
{
id: 5,
value: 1000
},
]
Whats the most optimal way I could implement this behavior ?
Thank you.
I'd do a single pass through the array remembering the first seen entry with a given value in a Map, marking that first entry (and any others) as duplicates if it's present, like this:
const map = new Map();
for (const entry of array) {
const previous = map.get(entry.value);
if (previous) {
previous.duplicate = entry.duplicate = true;
} else {
map.set(entry.value, entry);
}
}
Live Example:
const array = [
{
id: 1,
value: 10
},
{
id: 2,
value: 100
},
{
id: 3,
value: 10
},
{
id: 4,
value: 10
},
{
id: 5,
value: 1000
},
];
const map = new Map();
for (const entry of array) {
const previous = map.get(entry.value);
if (previous) {
previous.duplicate = entry.duplicate = true;
} else {
map.set(entry.value, entry);
}
}
console.log(array);
You can do this by first determining which are the duplicates, and then setting the 'duplicate' attribute.
counts = items.reduce((counter, item) => {
if (counter[item.value] != null) {
counter[item.value] += 1;
} else {
counter[item.value] = 1;
}
return counter;
}, {});
After this, you can go over your items, and if the count is >=2, set the 'duplicate' attribute.
items.forEach((item) => {
if (counter[item.value] > 1) {
item['duplicate'] = true;
}
});
You can use Array.map and Array.filter for that.
const input = [
{ id: 1, value: 10 },
{ id: 2, value: 100 },
{ id: 3, value: 10 },
{ id: 4, value: 10 },
{ id: 5, value: 1000 }
]
const output = input.map(entry => {
if (input.filter(x => x.value === entry.value).length > 1) {
return {
duplicate: true,
...entry
}
}
return entry
})
console.log(output)
I would create a map with value as the key, and a list of ids as the values, than after iterating over the whole map and creating the new mapping, unpack it back tothe desired form, and add duplicated for keys with more than one value.
I think this will help you. arr is your array.
arr.forEach(e=> {
const dublicatedDataLenth = arr.filter(a => a.value == e.value).length;
if(dublicatedDataLenth > 1){
e.dublicate = true;
}
})
It should be what you are looking for.
A copy from myself with a single loop and an object for storing seen values.
This approach returns a new array and does not mutate the given data.
var data = [{ id: 1, value: 10 }, { id: 2, value: 100 }, { id: 3, value: 10 }, { id: 4, value: 10 }, { id: 5, value: 1000 }],
result = data.map((seen => ({ ...o }) => {
if (o.value in seen) {
o.duplicate = true;
if (seen[o.value]) {
seen[o.value].duplicate = true;
seen[o.value] = false;
}
} else seen[o.value] = o;
return o;
})({}));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Javascript array of objects sum values for items key then sum values consecutively

I have the following array of objects:
const dataSet = [
{
createdOn: '2020-06-01',
id: 1,
value: 12
},
{
createdOn: '2020-06-01',
id: 2,
value: 23
},
{
createdOn: '2020-06-02',
id: 3,
value: 40
},
{
createdOn: '2020-06-03',
id: 4,
value: 15
}
]
What I'd like to be able to return is the follow:
[
{
createdOn: '2020-06-01',
value: 35 // sum of 12 + 23
},
{
createdOn: '2020-06-02',
value: 75 // sum of 35 + 40
},
{
createdOn: '2020-06-03',
value: 90 // sum of 75 + 15
}
]
So the outcome I'm after is:
When there are two (or more) objects with the same createdOn date,
then their values should be added together in a single object for that createdOn key.
Each object's value
also then needs to be added together, creating an accumulated
value from the previous object.
After a bit of searching through SO I found some code which helps me achieve the first point:
dataSet.reduce((acc, cur) => {
const date = cur.createdOn
const found = acc.find(elem => elem.createdOn === date)
if (found) {
found.value += cur.value
} else {
acc.push(cur)
}
return acc
}, [])
I just can't figure out how to achieve the accumulated value part.
Thanks in advance, and have a great weekend.
I would do this like this. First compute cumulative value for each distinct createdOn value and then transform resulting map back to array:
const map = dataSet.reduce((acc, {createdOn, value}) => {
acc[createdOn] = value + (acc[createdOn] || 0);
return acc;
}, {});
const result = Object.entries(map).map(([key, value], index) => ({
createdOn: key,
value: value
}));
result.forEach((item, index) => {
if (index) {
item.value += result[index - 1].value;
}
});
How about this one:
var dataSet = [ { createdOn: '2020-06-01', id: 1, value: 12 }, { createdOn: '2020-06-01', id: 2, value: 23 }, { createdOn: '2020-06-02', id: 3, value: 40 }, { createdOn: '2020-06-03', id: 4, value: 15 }];
var finalResult = Object.values(dataSet.reduce((acc, {createdOn, value})=>{
acc[createdOn] = acc[createdOn] || {createdOn, value:0};
acc[createdOn].value+=value;
return acc;
},{})).map((e,i,self)=>(e.value+=self[i-1] ? self[i-1].value : 0, e));
console.log(finalResult);

TypeError: counters.indexOf is not a function in React list

I have a list in state of a class and when a click event is called I need to get the selected component from the list. here is my code
class Counters extends Component {
state = {
counters: [
{ id: 1, value: 0 },
{ id: 2, value: 2 },
{ id: 3, value: 2 },
{ id: 4, value: 3 }
]
};
handleIncrement = counter => {
const counters = { ...this.state.counters };
console.log(counter); //this returns when this triggerrd {id: 2, value: 2}
const index = counters.indexOf(counter);
};
You are copying the values of an array into an object.
const counters = [...this.state.counters]

How to transform multidimensional array into chart data with es6 array methods

I need to convert one array to in specific data format to display the chart.
chrat.js library require data in this format
dataset = [ { label: 'one', data: []},
{label: 'two', data: []}
];
and I receive the response data in another format in random order so need to change appropriately with the respective label.
here is my code and trial.
const dataset = [
{
detail: {
team: [
{ name: 'alpha', game: 1 },
{ name: 'beta', game: 1 },
{ name: 'gamma', game: 1 },
{ name: 'delta', game: 1 },
{ name: 'echo', game: 1 }
]
}
},
{
detail: {
team: [
{ name: 'alpha', game: 2 },
{ name: 'beta', game: 2 },
{ name: 'echo', game: 2 },
{ name: 'gamma', game: 2 },
{ name: 'delta', game: 2 }
]
}
},
{
detail: {
team: [
{ name: 'echo', game: 1 },
{ name: 'delta', game: 0 },
{ name: 'beta', game: 0 },
{ name: 'gamma', game: 0 },
{ name: 'alpha', game: 0 }
]
}
},
{
detail: {
team: [
{ name: 'delta', game: 0 },
{ name: 'echo', game: 0 },
{ name: 'beta', game: 0 },
{ name: 'gamma', game: 1 },
{ name: 'alpha', game: 0 }
]
}
},
{
detail: {
team: [
{ name: 'delta', game: 0 },
{ name: 'echo', game: 0 },
{ name: 'alpha', game: 2 },
{ name: 'gamma', game: 3 },
{ name: 'beta', game: 2 }
]
}
},
{
detail: {
team: [
{ name: 'delta', game: 0 },
{ name: 'echo', game: 1 },
{ name: 'beta', game: 0 },
{ name: 'gamma', game: 2 },
{ name: 'alpha', game: 0 }
]
}
}
];
const teams = dataset.map(ds => ds.detail.team);
let z = teams.map(element => {
return element.map(e => {
let p = {};
let n = e.name;
let c = e.game;
p[n] = c;
return p;
});
});
console.log('z', z);
let nt = [];
z.reduce((c, n, i, a) => {
let z1 = n.map((i) => {
console.log(i);
let entries = Object.entries(i);
return entries.map((e) => {
return { label: e[0], data: e[1] };
});
});
return z1;
}, [])
desired output:
[
{
label: 'alpha',
data: [1, 2, 0, 0, 2, 0]
},
{
label: 'beta',
data: [1, 2, 0, 0, 2, 0]
},
{
label: 'gamma',
data: [1, 2, 0, 1, 3, 2]
},
{
label: 'delta',
data: [ 1, 2, 0, 0, 0, 0]
},
{
label: 'echo',
data: [1, 2, 1, 0, 0, 1]
}
]
I lost somewhere in the array.reduce method to achieve the output.
I am preferably looking for a es6 solution
any help is appreciated.
So I'm going to leave your dataset the same but lets start from the ground up and create some code to step through your data set and get to the desired output.
First we need to de-nest the data:
dataset.map(d => d.detail.team)
Now that we have teams lets reduce them all to a single array
dataset
.map(object => object.detail.team)
.reduce((acc, team) => acc.concat(team))
Okay good now we have one big set of names and games. We can now make this pretty easily into a hash
dataset
.map(object => object.detail.team)
.reduce((acc, team) => acc.concat(team))
.reduce((acc, team) =>{
acc[team.name] = acc[team.name] || []
acc[team.name].push(team.game)
return acc
}, {})
Now we have a hash of names to games. Calling Object.entries on this hash will give us pairs of lables
Object.entries(
dataset
.map(object => object.detail.team)
.reduce((acc, team) => acc.concat(team))
.reduce((acc, team) =>{
acc[team.name] = acc[team.name] || []
acc[team.name].push(team.game)
return acc
}, {})
)
Now we can map over these pairs to construct the final object
Object.entries(
dataset
.map(object => object.detail.team)
.reduce((acc, team) => acc.concat(team), [])
.reduce((acc, team) =>{
acc[team.name] = acc[team.name] || []
acc[team.name].push(team.game)
return acc
}, {})
)
.map(([team, games]) => ({ team, games }))
The real trick now is how many of these steps can be combined?
Well most of them! We can reduce this to looping over each object, referencing manually since we know structure, and then looping over each individual team array and finally constructing our hash.
Object.entries(
dataset
.reduce((acc, object) =>{
object.detail.team.forEach(team =>{
acc[team.name] = acc[team.name] || []
acc[team.name].push(team.game)
})
return acc
}, {})
)
.map(([team, games]) => ({ team, games }))
Extra Notes
Arrow Functions
We used arrow functions in this example to adhere to the request of using ES6 as much as possible. More information on arrow functions can be found on the MDN. Basically though it's another way to declare a function
function test(value){ return console.log(value) }
// same as
let test = value => console.log(value)
function add(a, b){ return a + b)
// same as
let add = (a,b) => a + b
Note the Array.prototype.forEach()
Now you'll notice we used an Array.prototype.forEach() in the combined example to manipulate the accumulator. That sentence should say all we need to there but for clarification for those who might not know, forEach is to be used when you want no return value and only want side effects. In this situation it's faster than attempting to actually return something since we don't want the overhead of discarding a bunch of arrays we've made when the end goal is to only change the way the accumulator looks.
That funky array being passed to a function
Ah yes, destructuring. Again more information can be found on the MDN. Basically it lets us pull values out of Objects or Arrays we know the structure of in advance. Note: Example courtesy of MDN article
var a, b, rest;
[a, b] = [10, 20];
console.log(a); // 10
console.log(b); // 20
[a, b, ...rest] = [10, 20, 30, 40, 50];
console.log(a); // 10
console.log(b); // 20
console.log(rest); // [30, 40, 50]
({ a, b } = { a: 10, b: 20 });
console.log(a); // 10
console.log(b); // 20
// Stage 3 proposal
({a, b, ...rest} = {a: 10, b: 20, c: 30, d: 40});
console.log(a); // 10
console.log(b); // 20
console.log(rest); // {c: 30, d: 40}
You can use Array.reduce(), to create a map and than use that map to get the desired output.
const dataset = [{detail:{team:[{name:'alpha',game:1},{name:'beta',game:1},{name:'gamma',game:1},{name:'delta',game:1},{name:'echo',game:1}]}},{detail:{team:[{name:'alpha',game:2},{name:'beta',game:2},{name:'echo',game:2},{name:'gamma',game:2},{name:'delta',game:2}]}},{detail:{team:[{name:'echo',game:1},{name:'delta',game:0},{name:'beta',game:0},{name:'gamma',game:0},{name:'alpha',game:0}]}},{detail:{team:[{name:'delta',game:0},{name:'echo',game:0},{name:'beta',game:0},{name:'gamma',game:1},{name:'alpha',game:0}]}},{detail:{team:[{name:'delta',game:0},{name:'echo',game:0},{name:'alpha',game:2},{name:'gamma',game:3},{name:'beta',game:2}]}},{detail:{team:[{name:'delta',game:0},{name:'echo',game:1},{name:'beta',game:0},{name:'gamma',game:2},{name:'alpha',game:0}]}}];
var map = dataset.reduce((a,curr)=>{
curr.detail.team.forEach((e)=> (a[e.name]= (a[e.name] || [])).push(e.game));
return a;
}, {});
var result =[];
Object.keys(map).forEach((key)=>{
result.push({
"label" : key,
"data" : map[key]
});
});
console.log(result);
You can use reduce to make a flat array and then loop over to get the wanted format
const dataset = [{detail:{team:[{name:'alpha',game:1},{name:'beta',game:1},{name:'gamma',game:1},{name:'delta',game:1},{name:'echo',game:1}]}},{detail:{team:[{name:'alpha',game:2},{name:'beta',game:2},{name:'echo',game:2},{name:'gamma',game:2},{name:'delta',game:2}]}},{detail:{team:[{name:'echo',game:1},{name:'delta',game:0},{name:'beta',game:0},{name:'gamma',game:0},{name:'alpha',game:0}]}},{detail:{team:[{name:'delta',game:0},{name:'echo',game:0},{name:'beta',game:0},{name:'gamma',game:1},{name:'alpha',game:0}]}},{detail:{team:[{name:'delta',game:0},{name:'echo',game:0},{name:'alpha',game:2},{name:'gamma',game:3},{name:'beta',game:2}]}},{detail:{team:[{name:'delta',game:0},{name:'echo',game:1},{name:'beta',game:0},{name:'gamma',game:2},{name:'alpha',game:0}]}}];
const flat = dataset.reduce( (a,b) => a.concat(b.detail.team), []);
let result = [];
for (let element of flat) {
let match = null;
for (let e of result) {
if (e.label === element.name) {
match = e;
}
}
if (match) {
match.data.push(element.game)
}
else {
result.push({
label : element.name,
data : [element.game]
});
}
}
console.log(result);
Another way: loop through the data set as it is, storing the results in a map dictionary-like object as well as in the array of results to be returned.
const dataset = [{detail:{team:[{name:'alpha',game:1},{name:'beta',game:1},{name:'gamma',game:1},{name:'delta',game:1},{name:'echo',game:1}]}},{detail:{team:[{name:'alpha',game:2},{name:'beta',game:2},{name:'echo',game:2},{name:'gamma',game:2},{name:'delta',game:2}]}},{detail:{team:[{name:'echo',game:1},{name:'delta',game:0},{name:'beta',game:0},{name:'gamma',game:0},{name:'alpha',game:0}]}},{detail:{team:[{name:'delta',game:0},{name:'echo',game:0},{name:'beta',game:0},{name:'gamma',game:1},{name:'alpha',game:0}]}},{detail:{team:[{name:'delta',game:0},{name:'echo',game:0},{name:'alpha',game:2},{name:'gamma',game:3},{name:'beta',game:2}]}},{detail:{team:[{name:'delta',game:0},{name:'echo',game:1},{name:'beta',game:0},{name:'gamma',game:2},{name:'alpha',game:0}]}}];
var result = [],
map = {};
dataset.forEach(a => {
a.detail.team.forEach(b => {
if (!(b.name in map)) {
map[b.name] = [];
result.push({
'label': b.name,
'data': map[b.name]
})
}
map[b.name].push(b.game);
});
});
console.log(result);
There's not much need to reduce or map any arrays here.

Categories