React: Reaching Nested State Items w/ Reducer - json

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.

Related

Access to an array inside json structure by value

How I can access to the models array when I have only the information "Bridgestone" or "Continental". I think that should works with Object.keys() and find() but all my tries didn't worked. I think the trick is to get the key and with this key you can iterate with forEach() the models.
json_structure = {
"tyres":[
{
"manufacture":"Bridgestone",
"models":[
"Potenza",
"Turanza"
]
},
{
"manufacture":"Continental",
"models":[
"Allseasonconta",
"Winter Contact"
]
}
]
}
Just use find method to find a manufacture by its name, and then get the models array out of it:
const jsonStructure = {
"tyres":[
{
"manufacture":"Bridgestone",
"models":[
"Potenza",
"Turanza"
]
},
{
"manufacture":"Continental",
"models":[
"Allseasonconta",
"Winter Contact"
]
}
]
}
const getModelsByManufactureName = data => name => {
const manufacture = data.tyres.find(val => val.manufacture === name)
if (!manufacture) return manufacture
return manufacture.models
}
console.log(getModelsByManufactureName(jsonStructure)('Bridgestone'))
Hope it helps :)
There is a way to do this without an explicit loop, but knowledge of the overall structure is still necessary:
json_structure.tyres.filter(o => o.manufacture == "Continental")[0].models
So the first step is to get to the "tyres" portion which is an array and then filter by "manufacture".
Then you can look at the first record (assuming "Continental" is unique and there is the "models" object.
Another more explicit way to do this would be similar to what you were proposing:
model = {};
json_structure.tyres.forEach(function(o) {
if(o.manufacture == "Continental") {
model = o.models; return;
}
})
model should contain the model information.

TypeError: Cannot read property 'reduce' of undefined in react

I have a form in which I am asking the user to input field values for a couple of fields, storing the field values in an state and displaying the state values in a customised format.
So, I have a couple of input fields and a submit button:
<button onClick={this.handleSubmit}>Submit</button>
{
this.state.credentials &&
//<Credentials value={this.state}/>
<Credentials value={JSON.stringify(this.state, undefined, 2)} />
}
The Credentials function convert the state of the component in JSON format:
const Credentials = ({value} ) => {
return <pre>{formatState(value)}</pre>;
}
The formatState function will basically manipulate the state values and display them in the way I want:
function formatState(state) {
console.log("hi")
console.log(state);
const output = state.groups.reduce((final, s)=> {
console.log(output)
const values = Object.keys(s).reduce((out, o)=> {
out[o] = s[o].map(k => Object.values(k))
return out;
}, {})
final = {...final, ...values}
return final;
}, {})
console.log(output)
}
The state looks like this:
{
"groups": [
{
"typeA": [
{
"name": "abc"
},
{
"number": "13,14"
}
],
"typeB": [
{
"country": "xyz"
},
{
"date1": "2019-05-14"
}
]
}
]
}
But I want the output like this:
groups: {
"typeA": [[abc],[13,14]],
"typeB": [[2019-05-14],[xyz]]
}
SO, reduce function is used to convert the state into the following output. But I getting the error :
"TypeError: Cannot read property 'reduce' of undefined"
Please can anybody tell me why this is happening.
Error is here <Credentials value={JSON.stringify(this.state, undefined, 2)} />. JSON.stringify produces string representaion of some object (this.state in your case). Argument state of formatState has type of string. It seems that you want to have state arguemnt to be object. So you should do
<Credentials value={this.state} />

How to override whole json document in mongodb update call

I must admit I am not exactly sure what is going on inside of the mongodbclient function update. In my code I currently have this:
app.post('/update', function(req, res) {
const params = req.body;
const newData = {
id: params.id,
data: params.data
a ton more fields will go here
};
db.collection('datas').update({id : params.id},
{ $set: {data : newData.data}}, (err, data) => {
if (!err) {
res.send({err: false});
} else {
res.send({err: true});
}
});
However I dont have the slightest idea what the whole $set bracket is doing or how I could modify it to override the entire document once there are a ton more fields on the incoming data. I suppose the id field really wont need to be updated but you can get the idea.
What is that bracket doing? And how can I customize it for my needs?
If you would like to override the entire document you can remove the $set tag.
$set simply sets the fields that you pass into it.
If you do not pass a $set or $unset command it will assume you are trying to overwrite the entire document.
Take the following document for example
{
_id: 1,
a: 1,
b: 2,
c: 3
}
Using set such as:
db.collection.update(
{_id: 1},
{$set: {
a:2,
b:4,
new: 123
}}
);
would return the following:
{
_id: 1,
a: 2,
b: 4,
c: 3,
new: 123
}
It updates a and b that were passed in but does not alter c as it was not passed in
using $unset will remove a field
db.collection.update(
{_id: 1},
{$unset: {
new: true
}}
);
would then return:
{
_id: 1,
a: 2
b: 4,
c: 3
}
Without passing in $set or $unset you can alter an entire document
db.collection.update(
{_id: 1},
{
comment: "what have I done to my data?!?!?!"
}
);
This will return, keeping _id unchanged but overwriting the entire document in the process
{
_id: 1,
comment: "what have I done to my data?!?!?!"
}
$set expects an hash. newData is already an hash, and it's the hash you need, so try:
$set: newData
Instead of
$set: {data: newData.data}
It should work.

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.