setState of dynamic multidimensional object - javascript

I have a state like this.
insetts: {
cid: {
checked: false,
value: "",
},
name: {
checked: false,
value: "",
},
...
},
outsetts: {
...
},
...
the event handler will receive the name of each level and update the fields checked and value of the checked item.
(the outer level is accessed through props - this.props.which)
I need to update
const target = evt.target.value;
this.state[this.props.which][target].checked
How can I use setState?
I've tried:
this.setState({
[this.props.which]: {
...this.state[this.props.which],
[evt.target.value]: {
...this.state[this.props.which][target],
checked: true
}
}
}
);
and some variants of it but to no avail.
Thanks
EDITED
it is working with the cloning approach recommended by #Vo Quoc Thang
const target = evt.target.value;
const cloned = JSON.parse(JSON.stringify(this.state[this.props.which]));
cloned[target].checked = true;
and both of the following approaches:
this.setState((prevState) => {
return {
...prevState,
[this.props.which]: cloned,
};
});
or
this.setState({ ...this.state, [this.props.which]: cloned });

You can clone the object first, modify it and update to your state
const attr = this.props.which;
const cloned = JSON.parse(JSON.stringify(this.state[attr]));
cloned[evt.target.value].checked = true;
this.setState({attr : cloned});

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> })

add the data every time the funtion is called into a array object of State

i am having this handleCheckClick funtion witch gets Data i want to store the data into a state every time the handleCheckClick funtion is called so after many times handleCheckClick is called the state should look like the object array below
handleCheckClick = (e, stateVal, index) => {
let prevState = [...this.state[stateVal]];
prevState[index].positive = e.target.checked;
console.log(index);
this.setState({ [stateVal]: prevState });
var date = moment(this.state.dateState).format("YYYY-MM-DD");
const { id, checked } = e.target.dataset;
console.log(stateVal);
if (e.target.checked) {
var checkbox = "True";
} else {
var checkbox = "False";
}
const Data = {
boolvalue: checkbox,
date: date,
userid: id,
};
this.setState({ datastate : Data });// something like this
};
after many times the handleCheckClick funtion is called the state must look like this
[
{
"date" : "2022-02-15",
"userid" : 6,
"boolvalue" : true
},
{
"date" : "2022-02-15",
"userid" : 5,
"boolvalue" : false
},
{
"date" : "2022-02-15",
"userid" :7,
"boolvalue" : true
},
{
"date" : "2022-02-15",
"userid" : 11,
"boolvalue" : true
},
{
"date" : "2022-02-15",
"id" : 4,
"boolvalue" : false
}
]
pls create a codesandbox example
https://codesandbox.io/s/recursing-wind-mjfjh4?file=/src/App.js
You have to take your data and call setState using the existing data merged with the new Data object. The merging can be done using ... (spread) operator. Here's the code with the relevant parts:
class Component extends React.Component {
handleClick = (e) => {
// build up a new data object:
if (e.target.checked) {
var checkbox = "True";
} else {
var checkbox = "False";
}
const { id } = e.target.dataset
var date = moment(this.state.dateState).format("YYYY-MM-DD");
const Data = {
boolvalue: checkbox,
date: date,
userid: id,
};
// set the new state, merging the Data with previous state (accesible via this.state)
// this creates a new array with all the objects from this.state.datastate and the new Dataobject
this.setState({
datastate: [...this.state.datastate, Data]
})
}
// log the state on each update for seeing changes.
componentDidUpdate() {
console.log('Component did update. State:', this.state)
}
// Rendering only a button for showcasing the logic.
render() {
return <button onClick={this.handleClick}></button>
}
constructor(props) {
super(props)
// initialise an empty state
this.state = {
datastate: [],
dateState: new Date()
}
}
}
Edit for removing an element when unchecked:
You can remove a certain element by its id in the onClick handler when the box is unchecked:
class Component extends React.Component {
handleClick = (e) => {
// get id first.
const { id } = e.target.dataset
// if element is not checked anymore remove its corresponding data:
if(e.target.checked === false) {
// remove the element by filtering. Accept everything with a different id!
const update = this.state.datastate.filter(d => d.userid !== id)
this.setState({
datastate: update
})
// end handler here..
return
}
// if we get here, it means checkbox is checked now, so add data!
var date = moment(this.state.dateState).format("YYYY-MM-DD");
const Data = {
// it is better to keep the boolean value for later use..
boolvalue: e.target.checked,
date: date,
userid: id,
};
// set the new state, merging the Data with previous state (accesible via this.state)
// this creates a new array with all the objects from this.state.datastate and the new Dataobject
this.setState({
datastate: [...this.state.datastate, Data]
})
}
// log the state on each update for seeing changes.
componentDidUpdate() {
console.log('Component did update. State:', this.state)
}
// Rendering only a button for showcasing the logic.
render() {
return <button onClick={this.handleClick}></button>
}
constructor(props) {
super(props)
// initialise an empty state
this.state = {
datastate: [],
dateState: new Date()
}
}
}
Feel free to leave a comment

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)
]
})
}

Change Nested State Value With A Single Function? (javascript/React)

I have some react user privilege state data I need to manage. I would like the ability to change the object privileges based on their property through a dynamic function. I'm not sure how to target the specific nested privilege property to change the value. Is this possible?
Question: How can I change the value of a nested privilege property to the functions type and value parameter?
Heres an Example:
const [userPrivilages, setUserPrivilages] = useState([{
_id: "123"
privilages: {
edit: true, //before!
share: true,
del: false
}
},
{
...more users
}
])
//my attempt
const changePrivilage = (type, value) => {
const newPrivilages = userPrivilages.map(user => {
return {
...user,
privilages: {
...privilages,
//change the privilage of "type" from the functions parameter to the value parameter
}
}) setUserPrivilages(newPrivilages)
}
changePrivilage("edit", false)
Desired output:
const [userPrivilages, setUserPrivilages] = useState([{
_id: "123"
privilages: {
edit: false, //After!
share: true,
del: false
}
},
{
...more users
}
])
Thanks!
You can use [] to refer to variable as a key like below:
const changePrivilage = (type, value) => {
const newPrivilages = userPrivilages.map(user => {
return {
...user,
privilages: {
...user.privilages,
[type]: value // here it is !!!
}
}) setUserPrivilages(newPrivilages)
}
Try this :
(see comments for understanding code)
const changePrivilage = (type,value) => {
const newUserPrivilages = userPrivilages.map(user => {
let newPrivilages = user.privilages; // get old privilages of user
newPrivilages[type] = value; // update type with new value
return {
...user,
privilages: newPrivilages, // set privilages as newPrivilages
};
});
setUserPrivilages(newUserPrivilages);
};
Note : this will change properties for all users. If you want to update only for specific user, pass _id as well to changePrivilage and execute newPrivilages[type] = value; // update type with new value inside if condition comparing user _id.

How do I update an array of objects in component state?

I am trying to update the property of an object which is stored in an array.
my state looks something like this:
state = {
todos: [
{
id: '1',
title: 'first item,
completed: false
},
{
id: '2',
title: 'second item,
completed: false
}
],
}
What I am trying to do is access the second element in the 'todos' array and update the completed property to either false -> true or true -> false.
I have a button with the handler for update, and my class method for the update looks like this:
onUpdate = (id) => {
const { todos } = this.state;
let i = todos.findIndex(todo => todo.id === id);
let status = todos[i].completed
let updatedTodo = {
...todos[i],
completed: !status
}
this.setState({
todos: [
...todos.slice(0, i),
updatedTodo,
...todos.slice(i + 1)
]
});
}
While this does work, I want to find out if there is a more concise way of achieving the same result; I tried to use Object.assign(), but that didn't work out because my 'todos' is an array, not an object. Please enlighten me with better code!
It would be best to use update function to make sure you don't work on outdated data:
onUpdate = (id) => {
this.setState(prevState => {
const copy = [...prevState.todos];
const index = copy.findIndex(t => t.id === id);
copy[index].completed = !copy[index].completed;
return { todos: copy }
})
}
You can simply copy your todos from state, then make edits, and after that put it back to the state
onUpdate = (id) => {
var todos = [...this.state.todos]
var target = todos.find(todo => todo.id == id)
if (target) {
target.completed = !target.completed
this.setState({ todos })
}
}

Categories

Resources