Dynamically setState() with key nested state - javascript

I have a method in a component. I want to dynamically setState with a key in a nested array of objects.
method = (name, value) => {
console.log(name)
//a //value is 1
//b //value is 2
//c //value is 3
this.setState({ [name]:value })
}
when its not nested, it dynamically changes state successfully. However when its nested
method = (name, value) => {
this.setState({
ArrayOfObjects:[{
[name] : value
}]
}
My state becomes
state = {
ArrayOfObjects: [{
c: 3
}]
}
I want
state = {
ArrayOfObjects: [{
a: 1,
b: 2,
c: 3
}]
What's wrong?

You could just push an element to the current ArrayOfObjects.
ArrayOfObjects = this.state.ArrayOfObjects;
ArrayOfObjects.push({[name] : value});
this.setState({
ArrayOfObjects
});
Or using the spread operator:
this.setState({
ArrayOfObjects: [
...this.state.ArrayOfObjects,
{[name] : value}
]
});

Assuming that ArrayOfObjects is an always an array with single object and that you want to merge name/value into that object:
method(name, value) {
// make a copy
const ArrayOfObjects = [...this.state.ArrayOfObjects];
// merge properties and set dynamic value
ArrayOfObjects[0] = { ...ArrayOfObjects[0], [name]: value };
this.setState({
ArrayOfObjects
});
}
Here is an example in action.
Hopefully that helps!

Related

Add dynamic key to set state, react

I have this state
this.state = {
dropdown1: false,
dropdown2: false,
dropdown3: false
}
I want to access to these dropdowns in state using this.setState but the number after 'dropdown' comes from API
onMaca = (ev) => {
this.setState({
dropdown + ev: true
})
}
So I want the key to be dynamic 'dropdown1' for example.
Thanks for your answers
you can access the object property like this object['property name']
onMaca = (ev) => {
this.state['dropdown' + ev]= true;
this.setState({
...this.state
})
}
https://codezup.com/add-dynamic-key-to-object-property-in-javascript/
You can use any of these to set key dynamically. I will try to update the answer with an example in a while for setState.
The state is a JS object, so you can get its keys as usual, like so:
const stateKeys = this.state.keys()
Now you have an array: [ "dropdown1", "dropdown1", "dropdown1" ]
One way to use it would be:
const keysMap = statekeys.map(( item, i ) => return {
key: item,
idx: i,
number: item.replace( /dropdown/, '' )
}
keysMap will look like so: [ { key: 'dropdown1', idx: 0, number "1" }, { key: 'dropdown1', idx: 1, number "2" }, { key: 'dropdown1', idx: 2, number "3" } ]
You can query keysMap for a given dropDownNumber like so:
let k = keysMap.find( kmap => kmap.key = dropDownNumber )
To set the dropdown's state:
this.setState({ k: <whatever> })

How to make sure Array has object items only with unique keys

I'm creating an online store. The product has attributes (in this case, iMac computer has attributes like: capacity, usb features and digital keyboard.)
I store the attributes in my state.
The problem is, every time I switch from, for example, 256GB capacity to 512GB capacity, it adds an entire new object with {capacity:"512GB"} to the array.
How do I configure my handler function (code below) so that it conditionally checks if the object in an array already has 'Capacity' key, and updates that to a new selection, instead of adding another object? I tried everything, I'm desperate
the handler receives object with key-value pair, and the key (as label) itself, and type. In this case, type can be ignored.
const handleAttributeChange = (object, type, label) => {
if (type == "text") {
this.setState({
selectedAttributes: {
id: id,
text: [...this.state.selectedAttributes.text, object],
swatch: this.state.selectedAttributes.swatch,
},
});
} else if ((type = "swatch")) {
this.setState({
selectedAttributes: {
id: id,
text: this.state.selectedAttributes.text,
swatch: [...this.state.selectedAttributes.swatch, object],
},
});
}
};
Instead of an array, you can take objects with diff key values. If keys are unique. Later u can convert it into the list of values.
let obj = {};
const update = (o) => {
obj = { ...obj, ...o };
};
/* In case you know key, value */
const update2 = (key, value) => {
obj[key] = value;
};
console.log(obj);
update({ a: 1 });
update({ b: 2 });
console.log(obj); // { a: 1, b: 2 }
update({ a: 3 });
console.log(obj); // { a: 3, b: 2 }
console.log(Object.entries(obj).map(([key, value]) => ({ [key]: value })));
//[ { a: 3 }, { b: 2 } ]

Updating nested state array react [duplicate]

If you have an array as part of your state, and that array contains objects, whats an easy way to update the state with a change to one of those objects?
Example, modified from the tutorial on react:
var CommentBox = React.createClass({
getInitialState: function() {
return {data: [
{ id: 1, author: "john", text: "foo" },
{ id: 2, author: "bob", text: "bar" }
]};
},
handleCommentEdit: function(id, text) {
var existingComment = this.state.data.filter({ function(c) { c.id == id; }).first();
var updatedComments = ??; // not sure how to do this
this.setState({data: updatedComments});
}
}
I quite like doing this with Object.assign rather than the immutability helpers.
handleCommentEdit: function(id, text) {
this.setState({
data: this.state.data.map(el => (el.id === id ? Object.assign({}, el, { text }) : el))
});
}
I just think this is much more succinct than splice and doesn't require knowing an index or explicitly handling the not found case.
If you are feeling all ES2018, you can also do this with spread instead of Object.assign
this.setState({
data: this.state.data.map(el => (el.id === id ? {...el, text} : el))
});
While updating state the key part is to treat it as if it is immutable. Any solution would work fine if you can guarantee it.
Here is my solution using immutability-helper:
jsFiddle:
var update = require('immutability-helper');
handleCommentEdit: function(id, text) {
var data = this.state.data;
var commentIndex = data.findIndex(function(c) {
return c.id == id;
});
var updatedComment = update(data[commentIndex], {text: {$set: text}});
var newData = update(data, {
$splice: [[commentIndex, 1, updatedComment]]
});
this.setState({data: newData});
},
Following questions about state arrays may also help:
Correct modification of state arrays in ReactJS
what is the preferred way to mutate a React state?
I'm trying to explain better how to do this AND what's going on.
First, find the index of the element you're replacing in the state array.
Second, update the element at that index
Third, call setState with the new collection
import update from 'immutability-helper';
// this.state = { employees: [{id: 1, name: 'Obama'}, {id: 2, name: 'Trump'}] }
updateEmployee(employee) {
const index = this.state.employees.findIndex((emp) => emp.id === employee.id);
const updatedEmployees = update(this.state.employees, {$splice: [[index, 1, employee]]}); // array.splice(start, deleteCount, item1)
this.setState({employees: updatedEmployees});
}
Edit: there's a much better way to do this w/o a 3rd party library
const index = this.state.employees.findIndex(emp => emp.id === employee.id);
employees = [...this.state.employees]; // important to create a copy, otherwise you'll modify state outside of setState call
employees[index] = employee;
this.setState({employees});
You can do this with multiple way, I am going to show you that I mostly used. When I am working with arrays in react usually I pass a custom attribute with current index value, in the example below I have passed data-index attribute, data- is html 5 convention.
Ex:
//handleChange method.
handleChange(e){
const {name, value} = e,
index = e.target.getAttribute('data-index'), //custom attribute value
updatedObj = Object.assign({}, this.state.arr[i],{[name]: value});
//update state value.
this.setState({
arr: [
...this.state.arr.slice(0, index),
updatedObj,
...this.state.arr.slice(index + 1)
]
})
}

React assign dynamic id in setState?

I have an array in the state. I am creating a method which adds a new row but how can i assign a dynamic id to the array in the setState?
This is what my state looks like:-
myArray: [
{ id:0, title:'Hello', function: 'none' }
]
And i have a method addRow that creates new Array.
addRow = () => {
const rows = [...this.state.myArray, {id: '' ,value:'',options: []}];
this.setState({
myArray: rows
});
}
How can i create a new dynamic id onClick of that method?
Easiest way is using length of the current array.
addRow = () => {
const rows = [...this.state.myArray, {id: this.state.myArray.length ,value:'',options: []}];
this.setState({
myArray: rows
});
}
There are 2 approaches which you can take:
Either add a counter variable this.setState({ count: this.state.count + 1 }) and increment it on every onClick.
OR
Use something like UUID import uuid from 'uuid' which will generate a unique id for you to use.
You can face asyc issues without functional setState, so you can do something like:
addRow = () => {
this.setState(previousState => {
const { myArray } = previousState
return { ...previousState, myArray: [ ...myArray, { id: myArray.length } ] }
});
}
For reference: https://reactjs.org/docs/faq-state.html#what-is-the-difference-between-passing-an-object-or-a-function-in-setstate

ReactJS: How to access and update nested state object with dynamic key?

Suppose I have a component with state defined as follows:
this.state = {
apple:{
a:1,
b:2,
},
mango:{
banana : {
a:1,
b:2,
}
}
}
If I wanted to update the value of a nested object in my state, I could do so with hard coded keys as shown below:
cost temp = { ...this.state['mango'] }
temp['banana']['a'] = 2;
this.setState({mango:temp});
How would I update a nested value in my state object dynamically key? For example, if I had a JSON path in either dot or array notation, how could I update my component state?
One way to achieve this would be to acquire the nested object that is the parent of the field that your path is targeting via Array#reduce:
const nestedObject = path
.slice(0, -1)
.reduce((object, part) => (object === undefined ? undefined : object[part]), { ...state })
And then update the last key/value of nestedObject by via the last key of your path:
/* Get last part of path, and update nestedObject's value for this key, to 2 */
const [pathTail] = path.slice(-1);
nestedObject[pathTail] = 2;
The following snippet shows these two ideas together:
/* Path of nested field to update, in array notation */
const path = ['mango', 'banana', 'a'];
/* Components state */
const state = {
apple: {
a: 1,
b: 2,
},
mango: {
banana: {
a: 1,
b: 2,
}
}
};
const stateClone = { ...state };
/* Aquire the parent object (ie banana) of the target field (ie a) */
const nestedObject = path
.slice(0, -1)
.reduce((object, part) => (object === undefined ? undefined : object[part]), stateClone)
if (nestedObject !== undefined) {
/* Obtain last key in path */
const [pathTail] = path.slice(-1);
/* Update value of last key on target object to new value */
nestedObject[pathTail] = 2;
}
/* Display updated state */
console.log('Updated state:', stateClone)
/* Call this.setState: */
// this.setState(stateClone);
Update
Here is some extra detail outlining how the reduce() part of the answer works:
path
/* slice obtains ['mango', 'banana'], seeing -1 clips last item */
.slice(0, -1)
/* reduce iterates through each part of array ['mango', 'banana']
where at each iteration we fetch the corresponding nested object
of the { ...state } object that's passed in */
.reduce((object, part) => {
/* At iteration 1:
object has two keys, 'apple' and 'mango'
part is 'mango'
object is defined, so return object['mango'] for first iteration
At iteration 2:
object passed from last iteration has one key, 'banana'
part is 'banana'
object is defined, so return object['banana'] for second iteration
Reduce complete:
we return object['banana'], which is the same as state['mango']['banana']
*/
if(object === undefined) { return undefined; }
return object[part]
}, stateClone)
Having:
const [formState, setFormState] = useState(
{
id:1,
name:'Name',
innerObjectName: {
propA: 'Something',
propB: 'Another thing',
}
});
Maybe you're looking for something like this:
const handleComplexInputChange = (evt, object) => {
setFormState({
...formState,
[object] : {
...formState[object],
[evt.target.name]: evt.target.value,
}
})
}
And from your component you should call it like this:
onChange={(e) => {
handleComplexInputChange(e, "innerObjectName");
}}

Categories

Resources