Updating state as a nested object - javascript

I have this type of state in my app
state = {
abc: true,
array: [
{ id: 12345, done: false },
{ id: 10203, done: false },
{ id: 54321, done: false }
]
};
I am looking for a solution to the following problem: I need to change done property accordingly to passed id like in the following function when something like this handle(12345) is passed as an argument to handle function :
handle = id => {
this.state.array.map(e => {
if (e.key === id) {
this.setState({array: [
{ id: id, done: true },
{ id: 10203, done: false },
{ id: 54321, done: false }
]})
}
});
};
In simple words I need to change just one object in array based on provided id.
Thanks for any help or tips!

I'd write the handle method as:
handle = id => {
this.setState(prevState => {
const { array } = prevState;
return {
array: [
...array.filter(o => o.id !== id),
{id, done: true}
]
};
});
};
The idea here is that, remove the matching id from old state, and then add a new object to the array with id and done property as {id, done: true}.

Once you are allowed to restructure state to be hashmap instead of array:
state = {
abc: true,
array: {
12345: { done: false },
10203: { done: false },
54321: { done: false }
]
};
then you will be able to use power of spread operator:
let id = 12345;
this.setState({
array: {
...this.state.array,
[id]: {
...this.state.array[id],
done: true
}
}
});
Otherwise using array.map() seems to be the only option

You can use this Redux pattern to return a new array with the element in question being changed, while keeping your array immutable:
handle = id => {
const updatedArray = this.state.array.map(e => {
if (e.key === id) {
return { id: id, done: true };
}
else {
return e;
}
});
this.setState({array: updatedArray});
};
This keeps your data structures immutable while changing only what you need to change.

var newArray = this.state.array;
for(var i = 0; i < newArray.length; i++){
if(newArray[i].id === 12345) {
newArray[i].done = true;
}
}
this.setState({array: newArray});
By creating the newArray here you will be avoiding directly touching the state element, so you can change anything you want inside it afterwards you can set the state.

Related

Inserting new element into empty JSON object

I'm trying to insert data into an empty JSON array, but am having trouble. I'm defining the array in the constructor, then making a couple get requests to the back-end when the page loads, and after getting the response I want to add the new array element to the existing. This is the code I am using:
constructor() {
super();
this.state = {
sds: []
}
}
componentDidMount() {
axios.get('/userData', {
params: {
user: this.props.auth.user.name
}
}).then(res => {
for(var i=0; i<res.data[0].chemID.split(',').length; i++){
if(res.data[0].chemID.split(',')[i] != 0){
axios.get('/chemData', {
params: {
id: res.data[0].chemID.split(',')[i]
}
//This is where I want to insert the data
}).then(res => this.sds += ({
id: i,
title: res.data[0].chemName,
selected: false,
key: 'sds'
}))
}
}
})
}
+= doesn't work like that. Use a spread operator to copy the previous contents of the array, then add the new object in manually -
}).then((res) => {
const newThing = {
id: i,
title: res.data[0].chemName,
selected: false,
key: 'sds'
};
this.setState(prevState => ({
sds: [...prevState.sds, newThing]
}))
}
You should never try to mutate the state yourself, always use setState. In this case, you can pass a function as the first parameter, which provides the previous state. That way, you can ensure whatever was in this.state.sds is preserved, and your new object is added to that array.
You can use array.push().
this.state.sds.push(obj);
If you are not using react setState method than, you need to refer any state variable using this.state.variableName.
You need to add to your array by using the push() method like this:
constructor() {
super();
this.state = {
sds: []
}
}
componentDidMount() {
axios.get('/userData', {
params: {
user: this.props.auth.user.name
}
}).then(res => {
for(var i=0; i<res.data[0].chemID.split(',').length; i++){
if(res.data[0].chemID.split(',')[i] != 0){
axios.get('/chemData', {
params: {
id: res.data[0].chemID.split(',')[i]
}
//This is where I want to insert the data
}).then(res => {
this.state.sds.push({
id: i,
title: res.data[0].chemName,
selected: false,
key: 'sds'
})
})
}
}
})
}
You can try using the next example:
this.state.sds[this.state.sds.length] = {
id: i,
title: res.data[0].chemName,
selected: false,
key: 'sds'
}
[Edited]
Like #larz said, you must use the setState method to avoid unexpected result of your code.
var newSds = this.stage.sds;
newSds[newSds.length] = {
id: i,
title: res.data[0].chemName,
selected: false,
key: 'sds'
};
this.setState({ sds: newSds});
You can get more information about the lifecycle in react here and the "state updates are merged" here

React.js state items array not updating properly even not showing any error

At first please see the picture for actually what's going on
The issue is while marked the checkbox then text erasing but I want to update a state field from state array & the functionalities are like below
state = {
items: [
{ id: 1, text: 'Buy milk', done: true },
{ id: 2, text: 'Deed cat', done: false },
{ id: 3, text: 'Wash floor', done: false }
]
};
markItemDone = (i) => {
this.setState(state => {
const items = state.items.map((item) => {
if (item.id === i){
return item.done = true;
} else {
return item;
}
});
return {
items,
};
});
}
JSX:
<input
type="checkbox"
onClick={() => this.markItemDone(item.id)}
defaultChecked={item.done ? true : null}
/>
I'm not finding the actual solution.
Thanks
In your code, you say return item.done = true;. This is returning a boolean instead of an item object and thus why you see 1: true in your screenshot. Instead, you want something like this:
if (item.id === i){
return {
...item,
done: true,
};
} else {
return item;
}
This will make a copy of the original item object, set its done field to be true, and return the new item.
This line in your map callback:
return item.done = true;
will map item to undefined for the item where id === i. Try revising your map callback as shown:
const items = state.items.map((item) => {
/* Return copy of item. If id === item.id, add done : true to
mapped result. For all other cases, ensure done is undefined */
return { ...item, done : id === item.id ? true : undefined };
});

How to change property values in an array Object?

I'm trying to change the values of each property in an array using a forEach function but not having any luck.
Here's my array:
this.panels = [
{ isRandomPanel : false },
{ isMyPanel : false },
{ isFavorite : false },
{ isEatable : false }
]
I'm trying to update the value of each property to true so finally I can get this:
isRandomPanel = true
isMyPanel = true
isFavorite = true
isEatable = true
I'm trying to use the forEach function but I'm stuck:
this.panels.forEach(panel => panel.isRandomPanel = true);
Does anyone know how to make this happen using pure Javascript, TypeScript or ES6?
If those are the only keys on the objects, you can iterate over Object.keys(panel) and set each panel[key] = true, like so:
var panels = [
{ isRandomPanel : false },
{ isMyPanel : false },
{ isFavorite : false },
{ isEatable : false }
];
// set all panel flags to true
panels.forEach(function (panel) {
Object.keys(panel).forEach(function (key) {
panel[key] = true;
});
});
console.log(panels);
Or, with shortened ES6 Syntax:
panels.forEach(panel => Object.keys(panel).forEach(key => panel[key] = true));
You need to itreate over the array, get each key from the object and set the value of that key to true. You can use Object.keys or for in loop to get the keys:
this.panels.forEach(panel => {
for(const key of Object.keys(panel))
panel[key] = true
})
OR
this.panels.forEach(panel => {
for(const key in panel)
panel[key] = true
})
As you are using ES 6, So following will work
panels.forEach(panel => Object.keys(panel).forEach(key => panel[key] = true));

Object push Firebase, how to remove key names from pushed items

I have this Object.key code that pushes all items:
const cloned_items = [];
Object.keys(items).sort().map(key => {
let item = {
[`item-${uid}`]: {
item: false
}
}
cloned_items.push({ ...item });
});
database.ref('/app/items').update({
...cloned_items
})
but this produces following result:
"0" : {
"timeslot-87dah2j" : {
item: false
}
},
"1" : {
"timeslot-7s1ahju" : {
item: false
}
}
instead of:
"timeslot-87dah2j" : {
item: false
},
"timeslot-7s1ahju" : {
item: false
}
any idea ?
It seems like you want to create a plain object, not an array.
In that case:
const cloned_items = Object.assign(...Object.keys(items).map(uid =>
({ [`item-${uid}`]: {item: false} })
));
NB: sorting is of no use when creating an object -- its keys are supposed to have no specific order.
You're creating an array of objects. Seems like you want to use .reduce() to create a single object from the array.
const cloned_items = Object.keys(items).sort().reduce((obj, key) =>
Object.assign(obj, { [`item-${uid}`]: { item: false } })
, {});
Your code doesn't show where uid is coming from, but I assume you meant key there, along with timeslot instead of item.
You may find Object.defineProperty to be cleaner, though you'll need to set up the property descriptor as you want it.
const cloned_items = Object.keys(items).sort().reduce((obj, key) =>
Object.defineProperty(obj, `item-${uid}`, {value:{item: false}})
, {});

React: Calling filter on Object.keys

A React component is passed a state property, which is an object of objects:
{
things: {
1: {
name: 'fridge',
attributes: []
},
2: {
name: 'ashtray',
attributes: []
}
}
}
It is also passed (as a router parameter) a name. I want the component to find the matching object in the things object by comparing name values.
To do this I use the filter method:
Object.keys(this.props.things).filter((id) => {
if (this.props.things[id].name === this.props.match.params.name) console.log('found!');
return (this.props.things[id].name === this.props.match.params.name);
});
However this returns undefined. I know the condition works because of my test line (the console.log line), which logs found to the console. Why does the filter method return undefined?
Object.keys returns an array of keys (like maybe ["2"] in your case).
If you are interested in retrieving the matching object, then you really need Object.values. And if you are expecting one result, and not an array of them, then use find instead of filter:
Object.values(this.props.things).find((obj) => {
if (obj.name === this.props.match.params.name) console.log('found!');
return (obj.name === this.props.match.params.name);
});
Be sure to return that result if you use it within a function. Here is a snippet based on the fiddle you provided in comments:
var state = {
things: {
1: {
name: 'fridge',
attributes: []
},
2: {
name: 'ashtray',
attributes: []
}
}
};
var findThing = function(name) {
return Object.values(state.things).find((obj) => {
if (obj.name === name) console.log('found!');
return obj.name === name;
});
}
var result = findThing('fridge');
console.log(result);
You need to assign the result of filter to a object and you get the result as the [id]. You then need to get the object as this.props.things[id]
var data = {
things: {
1: {
name: 'fridge',
attributes: []
},
2: {
name: 'ashtray',
attributes: []
}
}
}
var name = 'fridge';
var newD = Object.keys(data.things).filter((id) => {
if (data.things[id].name === name) console.log('found!');
return (data.things[id].name === name);
});
console.log(data.things[newD]);

Categories