updating all elments in an array - immutable.js

I have to update all the elements in an array using Immutablejs
The JSON looks like this :
imState = Immutable.fromJS({
app: {
line: {
name: "Bar Chart",
series: [
{
name: "Series1x",
color: "#82ca9d"
},
{
name: "Series2x",
color: "#2239ca"
},
{
name: "Series3x",
color: "#c2a5ca"
}
]
}
}
})
And I would simply like to iterate over all the series elements and change the color to a fixed color "#1bf115".
I am guessing you would use the update function. There is no API documentation on this function so I have been doing a lot of trial an error.
I tried to use it like this :
imState = imState.update(
['app', 'line', 'series'],
series => series.map(s => s.update('color', color => "#1bf115"))
)
However I get an undefined error at series.map.
Why is this wrong?

Because you are supplying a deeply nested path , instead of update use updateIn.
imState = imState.updateIn(
['app', 'line', 'series'],
series => series.map(s => s.update('color', color => "#1bf115"))
)

Related

Is there a way to successfully loop an Angular service call

How can I successfully run the following code?
onSubmit() {
let pdfData = [
{
field_name: 'data.Date',
value: this.freshDeskData.date,
placeholder: '',
page_no: 1,
},
{
field_name: 'data.Fullname',
value: "Bob Jones",
placeholder: '',
page_no: 1,
},
];
for(let i=0;i<pdfData.length;i++){
this.signHub.addPDFInfo(pdfData[i]).subscribe((data) => {
this.responseData = data[i]
});
}
}
Add PDF Service:
addPDFInfo(pdfInfo): Observable<PDFInfo> {
return this.http.put<PDFInfo>(
`${environment.apiUrl}/api/workflow/add-text-block?package_ID=${this.package_ID.data.package_id}&current_Document_ID=${this.current_Document_ID.data.documentid}`,
pdfInfo
);
}
The service is meant to loop through the JSON object and POST the information on the selected item to populate the related field on a PDF document. However, only one field is populated via the loop. The other remains empty.
You could achieve this by using the RxJs merge-operator, eg.:
onSubmit() {
let pdfData = [
{
field_name: 'data.Date',
value: this.freshDeskData.date,
placeholder: '',
page_no: 1,
},
{
field_name: 'data.Fullname',
value: "Bob Jones",
placeholder: '',
page_no: 1,
},
];
const requests = [];
for(let i=0;i<pdfData.length;i++){
requests.push(this.signHub.addPDFInfo(pdfData[i]));
}
merge(...requests).subscribe((data) => {
this.responseData = data[i]
})
}
granted you'd need to combine the results of each "next result" (that you're currently assigning to this.responseData). A candidate to solve that issue could be the reduce-operator
But, it's difficult to give a precise answer, without having knowledge about more of your codebase.

React: Reaching Nested State Items w/ Reducer

I'm working with some deeply nested state objects, and thanks to a lot of wisdom from the SO community, have switched to useReducer to manage the state. I can't figure out the syntax to reach deeply nested items or consecutive objects in arrays. Here's my state object:
const initialState = {
customer: [
{
name: "Bob",
address: "1234 Main Street",
email: "bob#mail.com",
phone: [
{
mobile: "555-5555",
home: "555-5555"
}
]
},
{
name: "Reggie",
address: "5555 Pineapple Lane",
email: "reggie#mail.com",
phone: [
{
mobile: "123-4567",
home: {
fax: "444-4444",
evening: "222-2222"
}
}
]
}
]
}
I need to reach our second customer, Reggie, and change both his address and home evening phone number (customer[1].address & customer[1].phone[1].home.evening)
I've tried this with my useReducer case statements, but no luck. . .I get a TypeError: Cannot read property 'object' of undefined when I call on these functions (presumably from my dropdown menu, who's value is set to this props I'm trying to change):
case "SET_CUST1_ADDRESS":
return {
...state,
customer: [
{ ...state.customer[1], address: value }
]
};
onChange={({ target: { value } }) =>
dispatch({ type: "SET_CUST1_ADDRESS", value })
}
And the second for the evening number. . .
case "SET_CUST1_PHONE_HOME_EVENING":
return {
...state,
customer: [
{
...state.customer[1],
phone: [
{
...state.customer[1].phone[0],
home: {
...state.customer[1].phone[0].home,
evening: value
}
}
]
}
]
};
onChange={({ target: { value } }) =>
dispatch({ type: "SET_CUST1_PHONE_HOME_EVENING", value
})
}
I understand the need to work with flattened state objects to make all this much easier, but unfortunately the data structure I'm working with is immutable. Tips/tricks/comments welcome. Thanks!
UPDATE:
It seems there is an unresolved conflict between using these two case statements, which is causing the TypeError: Cannot read property 'object' of undefined during my state changes. When I try to change the state of two different array objects in the same array, I get the error. Apparently these two cases cannot exist simultaneously:
case "SET_CUST_ADDRESS":
return {
...state,
customer: [
{ ...state.customer[0], address: value }
]
};
case "SET_CUST1_ADDRESS":
return {
...state,
customer: [
state.customer[0],
{ ...state.customer[1], address: value }
]
};
These two case statements seem to be causing an error with one another when I call the drop down function on either or, resulting in the aforementioned error. I don't see the connection. Any further input would be appreciated.
If I understand your question you miss this line:
case "SET_CUST1_PHONE_HOME_EVENING":
return {
...state,
customer: [
state.customer[0],
{
...state.customer[1],
phone: [
{
...state.customer[1].phone[0],
home: {
...state.customer[1].phone[0].home,
evening: value
}
}
]
}
]
};
I mean that you forgot to insert the customer[0] again to the state.
if this was not helpful- you can try to console.log the state before and after ans see which changes you don't want - and which code needs to be fixed according to that.
edit:
you can use something like this:
const stateCustomers = state.customer.slice();
let customer = stateCustomers.find(c => <your condition to find the desired customer>);
customer.phone[0].home.evening = value;
return {
...state,
customer: stateCustomers
};
Update:
Incase anyone is having a similar problem and having a hard time working with nested items, I would like to suggest first reading up on state immutability as recommended to me in my reddit thread on this topic.
I also found (via recommendation) a super way to deal with this type of nested state via something called immer. It's as easy as making an import statement and then wrapping your case statement in a 'produce' function, which makes a draft of your state that you can directly reference instead of spreading out all layers to make a change. So, instead of having something like this:
case "SET_CUST1_PHONE_HOME_EVENING":
return {
...state,
customer: [
state.customer[0],
{
...state.customer[1],
phone: [
{
...state.customer[1].phone[0],
home: {
...state.customer[1].phone[0].home,
evening: value
}
}
]
}
]
};
You can instead have something like this, which IMO is much cleaner and easier to read/write:
import { produce } from 'immer';
...
case "SET_CUST1_PHONE_HOME_EVENING":
return produce(state, draft => {
draft.customer[1].phone[0].home.evening = value
});
And boom! You're done.

Create keyed Maps from nested Lists with Immutable.js

I am working with a dataset that cannot be modified on the server side. So I am trying to setup the local data model on the client in a way that I can easily traverse through the model when updating parts of the data.
Therefore I am trying to create a multi-leveled Map from multi-leveled Maps including Lists, that themselves include Maps, etc. (see schematics at the end of this post).
What I am trying to get is a Map containing other Maps, with the key of the included Map being the value of the object (again please see schematics at the end of this post).
I got it to work on the first level:
const firstLevel = data.toMap().mapKeys((key, value) => value.get('value'));
See it in action here: https://jsfiddle.net/9f0djcb0/4/
But there is a maximum of 3 levels of nested data and I can't get my head around how to get the transformation done. Any help appreciated!
The schematic datasets:
// This is what I got
const dataset = [
{
field: 'lorem',
value: 'ipsum',
more: [
{
field: 'lorem_lvl1',
value: 'ispum_lvl1',
more: [
{
field: 'lorem_lvl2',
value: 'ispum_lvl2',
more: [
{
field: 'lorem_lvl3',
value: 'ispum_lvl3',
}
]
}
]
}
]
},
{
field: 'glorem',
value: 'blipsum'
},
{
field: 'halorem',
value: 'halipsum'
}
];
This is where I want to go:
// This is what I want
const dataset_wanted = {
ipsum: {
field: 'lorem',
value: 'ipsum',
more: {
lorem_lvl1: {
field: 'lorem_lvl1',
value: 'ispum_lvl1',
more: {
lorem_lvl2: {
field: 'lorem_lvl2',
value: 'ispum_lvl2',
more: {
lorem_lvl3: {
field: 'lorem_lvl3',
value: 'ispum_lvl3',
}
}
}
}
}
}
},
glorem: {
field: 'glorem',
value: 'blipsum'
},
halorem: {
field: 'halorem',
value: 'halipsum'
}
};
Retrieve nested structures using "getIn" is beter.
const data = Immutable.fromJS(dataset[0]);
const firstLevel = data.getIn(['more']);
const twoLevel = firstLevel.getIn([0,'more']);
const threeLevel = twoLevel.getIn([0,'more']);
console.log(firstLevel.toJS(),twoLevel.toJS(),threeLevel.toJS());
As for a more generative solution, I re-wrote the answer before to a recursive approach:
function mapDeep(firstLevel) {
return firstLevel.map((obj) => {
if (obj.has('more')) {
const sec = obj.get('more').toMap().mapKeys((key, value) => value.get('value'));
const objNext = mapDeep(sec);
obj = obj.set('more', objNext);
}
return obj;
});
}
The first level still needs to be mapped manually before.
const firstLevel = data.toMap().mapKeys((key, value) => value.get('value'));
const secondLevel = mapDeep(firstLevel);
Again, see it in action: https://jsfiddle.net/9f0djcb0/12/
This is good enough for me for now. Still feels like this can be solved smarter (and more performant).. Cheers :)
So after some time passed I came up with a solution that works for me:
let sec, third, objThird;
// 1st level: simple mapping
const firstLevel = data.toMap().mapKeys((key, value) => value.get('value'));
// 2nd level: walk through updated firstLevel's subobjects and do the mapping again:
const secondLevel = firstLevel.map((obj) => {
if (obj.has('more')) {
sec = obj.get('more').toMap().mapKeys((key, value) => value.get('value'));
// 3nd level: walk through updated secondLevel's subobjects and do the mapping again:
objThird = sec.map((o) => {
if (o.has('more')) {
third = o.get('more').toMap().mapKeys((key, value) => value.get('value'));
o = o.set('more', third);
}
return o;
});
obj = obj.set('more', objThird);
}
return obj;
});
See it in action here: https://jsfiddle.net/9f0djcb0/7/
This has been working nicely so far, thur pretty hard-coded. If anyone has a more elegant solution to this, I am happy to learn about it!

Redux: display single record but json is an array

My react-redux app is getting a single record in JSON but the record is an array and therefore it looks like this (notice [ ] brackets):
{"person":[{"PersonID":1,"Name":"John Smith","Gender":0}]}
So, the redux store shows it as person->0->{"PersonID":1,"Name":"John Smith","Gender":0}. As such, the state shows that the person object is empty:
Name: this.props.person?this.props.person.Name:'object is empty',
My PersonPage.js includes the details page like this:
<PersonDetail person={this.props.person} />
The details page has this:
import React from 'react';
import classnames from 'classnames';
class PersonDetail extends React.Component {
state = {
Name: this.props.person?this.props.person.Name:'',
PersonID: this.props.person?this.props.person.PersonID:null,
loading: false,
done: false
}
componentWillReceiveProps = (nextProps) => {
this.setState({
PersonID: nextProps.person.PersonID,
Name: nextProps.person.Name
});
}
This is my raw Redux state:
people: [
[
{
PersonID: 51,
Name: 'John Smith',
Gender: 0
}
]
]
Person is an array, that contains the object in which Name key is present, so you need to use index also, write it like this:
this.props.person && this.props.person.length ? this.props.person[0].Name : '';
Check this example:
var data = {
"person":[
{
"PersonID":1,
"Name":"John Smith",
"Gender":0
}
]
};
console.log('Name: ', data.person[0].Name);
I think that you are supposed to map the person detail foreach person's data.
on the PersonPage.js ,
map it as follows:
{
this.props.person.map((p)=>{
return (<PersonDetail person={p} />)
})
}
If I was you I would make an util function like this:
const parsePeople = people => {
if (people instanceof Array) return parsePeople(people.pop())
return people
}
const people = [
[{
PersonID: 51,
Name: 'John Smith',
Gender: 0
}]
]
const person = parsePeople(people)
console.log(person) -> Object
Using recursion we check if people is an instance of Array, we call the function again using people.pop() which return the last element of the array.
you have an array on your person data... you can only access that without the 0 using map...
example:
componentWillReceiveProps = (nextProps) => {
var PersonID = nextProps.person ? nextProps.person.map(item => { return item.PersonID}) : '';
var Name = nextProps.person ? nextProps.person.map(item => { return item.Name}) : '';
this.setState({
PersonID,
Name
});
}
this is considering you only have 1 array on person.
I fixed it! It was a combination of two of the answers given:
In the PersonPage.js, I had to call the PersonDetails object like this:
<PersonDetail
person={this.props.person[0]}
/>
And this is the new MapStatetoProps:
function mapStateToProps(state, props) {
const { match } = props;
if (match.params.PersonID) {
return {
person: state.people
}
}
Thanks to those who answered. This drove me nuts.

How to update an object within an array with immutable.js

I wish to update an objects in an array using immutable.js
My object looks like this:
{
location: '',
cars: [
{id: 1, color: blue},
{ id: 2, color: red}
]
};
In my code I get for example car id: 2 and change the color { id: 2, color: black}
I now wish to update the car which is at index 1 .
I tried :
const car = { id: 2, color: black}
updateIn([ 'cars' ], list => list.push(car));
This will only add a new car at the end.
Which function in Immutable JS can I use to update car id # 2.
What’s the correct way to do this with immutable.js
const car = { id: 2, color: black};
const indexToBeUpdated = 1;
updateIn(["cars", indexToBeUpdated], () => car);
whatever the index is, you need to pass it to updateIn methods first argument, which is an array.
Update element in immutable list by condition:
const data = fromJS({
location: '',
cars: [
{id: 1, color: blue},
{ id: 2, color: red}
]
});
const car = { id: 2, color: black}
const index = data.get(['cars']).findIndex(listItem => {
// condition here
return listItem.get('id') === car.id;
});
const new_data = data.setIn(['cars', index], car);