How to change a nested object value of a redux state - javascript

I have the following state
const state = {
courses: [],
series: [],
course: {
title: 'testing',
course_notes: [
{
id: 1,
note: "one" // want to edit this
},
{
id: 2,
note: "two"
}
]
}
}
I want to change state.course.course_notesp[0].name
I've never fully understood how this works, read a lot of tutorials, I feel I know how it works but it always trips me up. This is what I am trying
const m = {
...state,
course: {
course_notes:[
...state.course.course_notes,
state.course.course_notes.find(n => n.id === 1).note = "edited"
]
}
}
That seems to add edited as an extra node. state.course.course_notes.length ends up being 3.

You are using the spread operator for arrays like you would for objects.
Assume you have an object
const obj = { a: 1, b: 2 }
If you say:
{...obj, a: 2}
What you are saying is:
{ a: 1, b: 2, a: 2 }
The property a is defined twice, but the second one overrrides the first one.
If you do something similar for an array, however, the result would be different:
const arr = [1, 2];
const newArr = [...arr, arr[0]];
// here the result would be [1, 2, 1]
This is why when you are saying:
course_notes:[
...state.course.course_notes,
state.course.course_notes.find(n => n.id === 1).note = "edited"
]
what it does is add an extra element to the array.
What you should do is instead create a modified version of the array, for example using map
course_notes: state.course.course_notes.map(el => {
if (el.id === 1) {
el.note = 'edited';
}
return el;
});

There are lots of ways you could modify the state of your store to update one element of course_notes.
If we assumed the ids to be unique, I would map the previous array modifying the element with id 1.
....
course_notes: state.course.course_notes.map(x => x === 1
? { ...x, note: 'edited' }
: x
)
...

Related

Implementing a set that treats object equality on value not reference

I want to be able to implement a Set in javascript that allows me to do something like this:
const s = Set([[1,2,3], [1,2,3], 1, 2, 1]);
s.add([1,2,3]);
console.log(s);
// {[1,2,3], 1, 2}
Of course, since the === operator is used on the set, any object will not equal itself unless a reference to the same object is passed, and so instead of the above we would currently get:
Set(5) { [ 1, 2, 3 ], [ 1, 2, 3 ], 1, 2, [ 1, 2, 3 ] }
Does the following seem like a good way to implement this? What might I be missing or can improve on?
class MySet extends Set {
constructor(...args) {
super();
for (const elem of args) {
if (!this.has(elem)) super.add(elem);
}
}
has(elem) {
if (typeof elem !== 'object') return super.has(elem);
for (const member of this) {
if (typeof member !== 'object') continue;
if (JSON.stringify(member) === JSON.stringify(elem))
return true;
}
return false;
}
add(elem) {
return (this.has(elem)) ? this : super.add(elem);
}
delete(elem) {
if (typeof elem !== 'object') return super.delete(elem);
for (const member of this) {
if (typeof member !== 'object') continue;
if (JSON.stringify(member) === JSON.stringify(elem))
return super.delete(member);
}
return false;
}
}
Assuming the provided objects don't contain values that cannot be stringified to JSON (function, undefined, symbol, etc.) You can use JSON.stringify().
One problem you might encounter is that stringifying { a: 1, b: 2 } doesn't produce the same result as { b: 2, a: 1 }. A fairly easy way to solve this would be to stringify the object and make sure the resulting JSON has properties placed in alphabetical order.
For this we can look to the answer provided in sort object properties and JSON.stringify.
I also think you are over complicating things by only stringifying values if they are an object. Instead you could just stringfy everything, null would result in "null", "string" would result in '"string"', etc. This simplifies the code by a lot. The only restriction then becomes that all values must be a valid JSON value.
// see linked answer
function JSONstringifyOrder(obj, space)
{
const allKeys = new Set();
JSON.stringify(obj, (key, value) => (allKeys.add(key), value));
return JSON.stringify(obj, Array.from(allKeys).sort(), space);
}
class MySet extends Set {
// The constructor makes uses of add(), so we don't need
// to override the constructor.
has(item) {
return super.has(JSONstringifyOrder(item));
}
add(item) {
return super.add(JSONstringifyOrder(item));
}
delete(item) {
return super.delete(JSONstringifyOrder(item));
}
}
const set = new MySet([[1,2,3], [1,2,3], 1, 2, 1]);
set.add([1,2,3]);
set.add({ a: { s: 1, d: 2 }, f: 3 });
set.add({ f: 3, a: { d: 2, s: 1 } });
// Stack Overflow snippets cannot print Set instances to the console
console.log(Array.from(set));
// or unserialized
Array.from(set, json => JSON.parse(json)).forEach(item => console.log(item));

Removing duplicates with in an object array using angular 4

I have an array with below list of items as shown in image , I would like to remove the duplicates
[L7-LO, %L7-LO] from that array.
I have tried with the following conditions:
Scenario 1 :
this.formulalist.filter((el, i, a) => i == a.indexOf(el))
Scenario 2:
Observable.merge(this.formulalist).distinct((x) => x.Value)
.subscribe(y => {
this.formulalist.push(y)
});
Scenario 3:
this.formulalist.forEach((item, index) => {
if (index !== this.formulalist.findIndex(i => i.Value == item.Value))
{
this.formulalist.splice(index, 1);
}
});
None of the three scenarios above were able to remove the duplicates from that array. Could any one please help on this query?
angular is not necessary use vanillajs
filter the elements with only one occurrence and add to the new list the first occurrence
let newFormulalist = formulalist.filter((v,i) => formulalist.findIndex(item => item.value == v.value) === i);
Try populating a new array without duplicates. Assign the new array later to formulalist.
newArr = []
this.formulalist.forEach((item, index) => {
if (this.newArr.findIndex(i => i.Value == item.Value) === -1)
{
this.newArr.push(item)
}
});
this.formulalist = this.newArr
EDIT
Looking at the answer above, the solution seems so outdated. A better approach would have been to use an Array.filter() than a Array.forEach().
But, having a better solution would be nice, now when I see this question, I feel findIndex() not to be a good approach because of the extra traversal.
I may have a Set and store the values in the Set on which I want to filter, If the Set has those entries, I would skip those elements from the array.
Or a nicer approach is the one that is used by Akitha_MJ, very concise. One loop for the array length, an Object(Map) in the loop with keys being the value on which we want to remove duplicates and the values being the full Object(Array element) itself. On the repetition of the element in the loop, the element would be simply replaced in the Map. Later just take out the values from the Map.
const result = Array.from(this.item.reduce((m, t) => m.set(t.name, t), new Map()).values());
Hope this works !!
// user reduce method to remove duplicates from object array , very easily
this.formulalist= this.formulalist.reduce((a, b) => {
if (!a.find(data => data.name === b.name)) {
a.push(b);
}
return a;
}, []);
// o/p = in formulalist you will find only unique values
Use a reducer returning a new array of the unique objects:
const input = [{
value: 'L7-LO',
name: 'L7-LO'
},
{
value: '%L7-LO',
name: '%L7-LO'
},
{
value: 'L7-LO',
name: 'L7-LO'
},
{
value: '%L7-LO',
name: '%L7-LO'
},
{
value: 'L7-L3',
name: 'L7-L3'
},
{
value: '%L7-L3',
name: '%L7-L3'
},
{
value: 'LO-L3',
name: 'LO-L3'
},
{
value: '%LO-L3',
name: '%LO-L3'
}
];
console.log(input.reduce((acc, val) => {
if (!acc.find(el => el.value === val.value)) {
acc.push(val);
}
return acc;
}, []));
if you are working using ES6 and up, basic JS using map and filter functions makes it easy.
var array = [{value:"a"},{value:"b"},{value:"c"},{value:"a"},{value:"c"},{value:"d"}];
console.log(array.filter((obj, pos, arr) => {
return arr.map(mapObj => mapObj["value"]).indexOf(obj["value"]) === pos;
}));
Filtering for unique values is much faster with assigning values to some object properties - there not will be duplicates.
This approach gets better and better with every +1 member of initial array, because looping will be causing fast algorithm complications
let arr = [
{value: 'L7-LO', name: 'L7-LO'},
{value: '%L7-LO', name: '%L7-LO'},
{value: 'L7-LO', name: 'L7-LO'},
{value: '%L7-LO', name: '%L7-LO'},
{value: 'L7-L3', name: 'L7-L3'},
{value: '%L7-L3', name: '%L7-L3'},
{value: 'LO-L3', name: 'LO-L3'},
{value: '%LO-L3', name: '%LO-L3'}
];
let obj = {};
const unique = () => {
let result = [];
arr.forEach((item, i) => {
obj[item['value']] = i;
});
for (let key in obj) {
let index = obj[key];
result.push(arr[index])
}
return result;
}
arr = unique(); // for example;
console.log(arr);

Best way to separate a list into two new list based on a condition

I am looking for nice good method to split array to two new arrays based on a condition.
Lets assume we have a list containing same structure objects, the object has a bool property, lets call it condition and we want two new list where we have only the same condition elements.
const cont1 = myArray.filter((el) => el.condition)
const cont2 = myArray.filter((el) => !el.condition)
This could work I guess but I am wondering if there is better single iteration verzion of this.
One option would be to use reduce, which will functionally separate all the items in only a single iteration over the array:
const myArray = [
{ condition: true },
{ condition: true },
{ condition: false },
]
const [cont1, cont2] = myArray.reduce(([cont1, cont2], item) => {
(item.condition ? cont1 : cont2).push(item);
return [cont1, cont2];
}, [[], []]);
console.log('cont1: ', cont1);
console.log('cont2: ', cont2);
Or, less functionally but perhaps more readably, you can .push to outer variables:
const myArray = [
{ condition: true },
{ condition: true },
{ condition: false },
];
const cont1 = [];
const cont2 = [];
myArray.forEach((item) => {
(item.condition ? cont1 : cont2).push(item);
});
console.log('cont1: ', cont1);
console.log('cont2: ', cont2);
Reduce could work, but reducing onto an object, with 2 different arrays as object members:
const arr = [1,2,3];
arr.reduce((acc, next) => {
if (next % 2 === 1){
acc.odd.push(next);
} else {
acc.even.push(next);
}
return acc;
}, { odd: [], even: [] }); // { even: [2], odd: [1,3] }
You can do it in a single line like this:
myArray.forEach(el=>el.condition?cont1.push(el):cont2.push(el));

mapping one object to another using some functional programming

I would like to map one array of object into another in a more functional style, I am using typescript.
Basically I am using delete to remove a property on a object, I would like to know if there is a better way to write it.
const data = props.data.map(d => ({
order: d.position,
logs: d.batches.map(b => {
let log= {
amount: b.scrap,
batchNumber: '', // NO GOOD
}
if (!b.batch || b.batch.length === 0) {
delete log.batchNumber // NO GOOD
}
return log
}),
}))
example input data:
const data = [
position: 1,
batches: [
{batchNumber: '', ammount: 3}
]
]
result:
const data = [{
order: 1,
logs:[ {ammount:3}]
}
]
You can do another map on the batches to return a new array of objects, and attach that to your returned object instead:
const out = data.map(({ position: order, batches }) => {
const logs = batches.map(({ batchNumber, ammount }) => {
if (batchNumber) return { batchNumber, ammount };
return { ammount };
});
return { order, logs }
});
DEMO
One approach would be to make a shallow copy of the target omitting keys you want to delete, for example:
let drop = key => obj => Object.keys(obj).reduce((r, k) =>
k === key ? r : {...r, [k]: obj[k]}, {});
let test = [
{foo:11, bar:2, baz: 3},
{foo:22, bar:2, baz: 3},
{foo:33, bar:2, baz: 3},
];
console.log(test.map(drop('bar')));
To add another option to the mix: it is possible to use Object.assign to optionally assign the property:
const data = [{
position: 1,
batches: [{batchNumber: '',ammount: 3}, {batchNumber: 'withNr',ammount: 4}]
}];
const res = data.map(d =>
({
order: d.position,
logs : d.batches.map(({ammount, batchNumber}) => Object.assign({ammount}, batchNumber ? {batchNumber} : null ))
})
);
console.log(res);

How to use if statement within map in javascript/vuejs

I'm building a small application in Vuejs where I'm getting a response data and I'm mapping it to a variable, I've got few elements which has empty array, so while mapping I want to check the condition and map accordingly. Here is my code:
this.model = a.map(i => Object.assign({
'id': i.id,
'meeting_date': i.schedule,
'meeting_call': i.type,
'event_type': i.event_type,
'venue': i.venue,
'with_client': i.with_client
},{
if(i.meeting.meeting_summaries)
{
'meeting_summaries': i.meeting_summaries.map(ms => ({
client_name: ms.client_name,
nature: ms.nature,
action: ms.action,
mention: ms.user_id,
feedback: ms.feedback
}))
}
},
map is purely functional, it doesn't modify the elements instead return a newly formed array, so you can do like this:
this.model = a.map(i => {
var item = {}
item['id']= i.id,
item['meeting_date']= i.schedule,
item['meeting_call']= i.type,
item['event_type']= i.event_type,
item['venue']= i.venue,
item['with_client']= i.with_client
if(i.meeting && i.meeting.meeting_summaries) {
item['meeting_summaries']= i.meeting.meeting_summaries.map(ms =>({
client_name: ms.client_name,
nature: ms.nature,
action: ms.action,
mention: ms.user_id,
feedback: ms.feedback
}))
}else {
item['meeting_summaries'] = []
}
return item
}
In your case you can just swap to ternary expression:
this.model = a.map(i => Object.assign({
'id': i.id,
'meeting_date': i.schedule,
'meeting_call': i.type,
'event_type': i.event_type,
'venue': i.venue,
'with_client': i.with_client
}, (i.meeting.meeting_summaries) ? { // if condition is met
'meeting_summaries': i.meeting_summaries.map(ms => ({
client_name: ms.client_name,
nature: ms.nature,
action: ms.action,
mention: ms.user_id,
feedback: ms.feedback
}))
} : {} // otherwise don't do anything
The idea is the following:
const a = [1, 2, 3, 4, 5];
const b = a.map(number => Object.assign({a: number},
(number > 2) ? {b: ++number} : {}, // add 'b' for numbers > 2
(number % 2 === 0) ? {c: number + ' is even'} : {} // add 'c' for even numbers
))
console.log(b)

Categories

Resources