I actually figured out how to get the result I need, but I was hoping that someone could breakdown whats going on here and why the other more straightforward paths I tried didnt work.
essentially I was needing to set some nested state at various levels using some variables(namely id of the element)
so I tried this way
handleClick = (e) => {
var x = e.target.id
console.log(this.state.fields[x])
!this.state.fields[x].disabled ? this.setState({[this.state.fields[x].disabled]:true}) : this.setState({[this.state.fields[x].disabled]:false})
}
This for whatever reason creates a state object "false" at the top level. This is weird to me because the console logs this.state.field[x] correctly.
I also tried the same but with
setState({[this.state.fields.x.disabled]:true})
and
setState({[fields.x.disabled]:true})
both of which wouldnt compile. Not to mention I cant even figure out how to easily update nested state properties...Surely there is an easy way!
Im pretty new to react so any explaination as to what the problems are here
would be greatly appreciated. Ill post my solution(workaround)
created a new object and popped it back in place
i guess I would be ok with this except for the fact the fields is a pretty big dictionary as is(over 100 sub dicts) and copy and pasting that many entries sounds expensive
seems like alot of work just to flip one boolean property
handleClick = (e) => {
var x = e.target.id
//console.log("...",this.state.fields[x].disabled)
if(!this.state.fields[x].disabled) {
var cat = Object.assign({},this.state.fields)
cat[x].disabled = true;
this.setState({fields:cat})
}
}
fields.x accesses a property with 'x' name, it's not the same as fields[x].
Updater function should be used if updated state uses previous state. State mutation should be generally avoided because this may result in unexpected behaviour.
It likely should be:
setState(({ fields }) => ({
fields: {
...fields,
[x]: {
...fields[x],
disabled: true
}
}
}));
// Assuming your state is like this:
state = {
fields: {
"fieldId-1": {
disabled: false
},
"fieldId-2": {
disabled: true
}
}
};
So, you are basically passing value of 'this.state.fields['fieldId-1'].disabled === false' here and actually doing this in your setState call:
this.setState({false: false});
which definitely will create a new field in your state object at top level
and same goes with rest of your cases.
If you want to update the nested property e.g 'fieldId-1', then one possible way would be by merging your changes gracefully using updater function.
this.setState(prevState => ({
fields: {
...this.state.fields,
[x]: { disabled: !prevState.fields[x].disabled }
}
}));
};
I have also created a codeSandbox demo to demonstrate my point. Hope it helps. Happy Coding :)
Related
To better understand the question open please these two sandboxes i made. One is written with react and the other with vanilla js.
React: https://codesandbox.io/s/boxes-v2-w400we (ignore any maximum call errors, idk what they want from me lol)
Vanilla: https://codesandbox.io/s/boxes-vanilla-js-z1z26l (refresh inside browser if boxes don't show up)
The problem is that react one is very slow. And not only state change, but even class toggle. I tried to separate these functions, so it at least look fast for user, even if state change need some more time behind the scenes, but it doesn't work somehow.
On the other hand vanilla one. Yes i'm mutating object there and it's not cool, but it works almost instantly even with 10k elements!
How to achieve same speed with react? And if this is not possible, then how to at least change color fast to hide slow state changing?
So, basically i need to change this function below (from react sandbox) to something that doesn't map through whole array, finding id, copying everything just to change one item. Since we know the id already and this id match index of this item in array, why can't we just change it directly like i did in vanilla version?
const updateData = id => {
setData(prevState => {
return prevState.map(el => {
return el.id === id ? { ...el, active: !el.active } : el;
});
});
};
I tried this, but it's still slow
const updateData = id => {
setData(prevState => {
const item = prevState[id];
const newState = [...prevState];
newState[id] = {...item, active: !item.active}
return newState;
})
}
I have an object variable within the state of my react app. I initialize it with the structure that I want the object to have. Late I am trying to update that object using the setState function. My issue is, I can't get it to actually update anything within the state. The way my code currently looks is:
// the initial state
var initialState = {
object: {
foo: '',
bar: 0,
array: [<an array of objects>]
}
};
// in componentDidMount
this.state = initialState;
// updating the object that is within the state (also in componentDidMount)
let object = {
foo: 'Value',
bar: 90,
array: [<a populated array of objects>]
};
this.setState(prevState => ({
...prevState.object,
...object
}));
console.log(this.state.object); // returns the object defined in initial state
I am really not sure how to fix this issue. I have also been trying a few other methods (especially the ones outlined in this post: Updating an object with setState in React
I have not tried every method outlined in that post but the ones I have been trying have not been working and I figure that this means it is a mistake that does not involve which method I am using. Any insight into this issue would be greatly appreciated. I tried to make this code as concise as possible but if you want to see the exact code I am using (which is pretty much just this but more) just ask.
Edit 2
You have to care each object key equality.
You can do this.
this.setState((prevState) => {
console.log('logging state: ', prevState)
return {
...prevState.object,
object:{ ...object }
})
)}
I am trying to implement a table in react where the user can edit individual rows by clicking the edit button on a row and then submit once he has made his change. I have say two components App.js and its child Table.js to implement this.
The way I thought of doing this initially was letting each of this component have their own state for rows and then the Table component reads from the props send to it by parent initially and only change the parent rows when users submits the change as oppose to onChange event. But I've read that reading props into state is an anti-pattern.
So decided to have everything in the parent by having two values for row (oldrows,newrows). And using them to maintain state instead, This is the design I came up with :
But what happens is whenever I click cancel the oldRows get bound to the newRows, here is a codePen example I put up:
https://codepen.io/snedden-gonsalves/pen/zYOVMWz
handleChangeRowInput = (event, keyValue) => {
let keyVals = [...this.state.newValuesArray];
keyVals[this.state.editIndex][keyValue] = event.currentTarget.value;
this.setState({
newValuesArray: keyVals
})
}
handleCancelRowInput = () => {
this.setState({
newValuesArray: [...this.state.oldValuesArray],
editIndex: -1
})
console.log('array', this.state.newValuesArray)
}
handleSubmitRowInput = () => {
this.setState({
oldValuesArray: [...this.state.newValuesArray],
editIndex: -1
})
}
In the codePen example if you enter a new value then cancel and then try adding a new value again the the old values and new values get bound.
I tried using lodash deepClone but it didn't work out, not sure why this is happening.
Also if you could comment on what is the best way to design this in react that would be awesome as I am very new to react and just trying to learn ..
I didn't find any issue after the cancel function. For me, the issue was coming up after I called the save function.
After clicking on the save button and then editing again, the old values and new values were get bound.
The handleSubmitRowInput function should create a new array for the oldValuesArray using the cloneDeep function
handleSubmitRowInput = () => {
this.setState({
oldValuesArray: _.cloneDeep(this.state.newValuesArray),
editIndex: -1
})
}
I must be missing something obvious here. I have a to-do list app which uses a function for creating new lists. Once createList is called I want to then highlight the list by setting its selected prop to true So below are the two methods for doing this. I'm trying to call one after the other. Both of them modify state using the appropriate callback that uses prevState, yet for whatever reason createList does not set the new list in state before toggleSelected gets called, and so listName is undefined in toggleSelected. Is there anyway to ensure the new list object is set in state before calling toggleSelected? I should probably be using Redux but I didn't want to get into it for my first React app.
createList = (listName) => {
const lists = {...this.state.lists};
lists[listName] = {
listName: listName,
selected: false,
todos: {}
};
this.setState(prevState => {
return {lists: prevState.lists};
});
};
toggleSelected = (listName) => {
let selected = this.state.lists[listName].selected;
selected = !selected;
this.setState(prevState => {
return {
bookLists: update(prevState.lists, {[listName]: {selected: {$set: selected}}})
};
});
};
Both methods are called in another component like so after an onSubmit handler with the new list name being passed in:
this.props.createList(newListName);
this.props.toggleSelected(newListName);
PS - If you're wondering what's up with update(), it's from an immutability-helper plugin that allows for easily setting nested values in a state object(in this case, state.lists[listName].selected)--another reason I probably should have gone with Redux.
PPS - I realize I can just set the new list's selected prop to true from the start in creatList but there's more to the app and I need to set it after creation.
Don't do what you're doing in toggleSelected right now, instead toggle the selected flag in your list (without extracting it) and then let your component know you updated the lists data by rebinding the resulting object:
class YourComponent {
...
toggleSelected(listName) {
let lists = this.state.lists;
let list = lists[listName];
list.selected = !list.selected;
this.setState({ lists });
}
..
}
Then make sure that in your render function, where you create the UI for each list, you check whether selected is true or false so you can set the appropriate classNames string.
(Also note that in your code, you used selected = !selected. That isn't going to do much, because you extracted a boolean value, flipped it, and then didn't save it back to where it can be consulted by other code)
The problem is not in the second setState function. It is at the first line of the toggleSelected() method.
When the toggleSelected() method is executed, the first setState haven't been executed.
The flow of the your code is:
createList();
toggleSelected();
setState() in createList();
setState() in toggleSelected();
Solution 1:
Use await and async keywords
Solution 2:
Use redux
My code works, but I have a best practice question: I have an array of objects in the state, and a user interaction will change a value of one object at a time. As far as I know, I'm not supposed to change the state directly, i should always use setState instead. If I want to avoid that with any price, I will deep clone the array by iteration, and change the clone. Then set the state to the clone. In my opinion avoiding to change the state that I will change later anyway is just decreasing my performance.
Detailed version:
this.state.data is an array of objects. It represents a list of topics in a forum, and a Favorite button will toggle, calling clickCollect().
Since I have an array in the state, when I change the is_collected property of one item, I need to create a copy of the array to work with, and after changing to the new value, I can set it to the state.
var data = this.state.data.slice(0);
data[index].is_collected = !data[index].is_collected;
this.setState({data: data});
var data = this.state.data : This would copy the pointer to the array and push(), shift(), etc would alter the state directly. Both data and this.state.data will be affected.
var data = this.state.data.slice(0) : This makes a shallow clone, push and shift doesn't change the state but in my clone I still have pointers to the elements of the state's array. So if I change data[0].is_collected, this.state.data[0].is_collected gets changed as well. This happens before I call setState().
Normally I should do:
var data = [];
for (var i in this.state.data) {
data.push(this.state.data[i]);
}
Then I change the value at index, setting it to true when it's false or false when it's true:
data[index].is_collected = !data[index].is_collected;
And change state:
this.setState({data: data});
Consider my array is relatively big or enormously big, I guess this iteration will reduce the performance of my APP. I would pay that cost if I knew that it is the right way for any reason. However, in this function (clickCollect) I always set the new value to the state, I'm not waiting for a false API response that would say to stop making the change. In all cases, the new value will get into the state. Practically I call setState only for the UI to render again. So the questions are:
Do I have to create the deep clone in this case? (for var i in ...)
If not, does it make sense to make a shallow clone (.slice(0)) if my array contains objects? The changes are being made on the objects inside of the array, so the shallow clone still changes my state, just like a copy (data = this.state.data) would do.
My code is simplified and API calls are cut out for simplicity.
This is a beginner's question, so a totally different approach is also welcome. Or links to other Q & A.
import React from 'react';
var ForumList = React.createClass({
render: function() {
return <div className="section-inner">
{this.state.data.map(this.eachBox)}
</div>
},
eachBox: function(box, i) {
return <div key={i} className="box-door">
<div className={"favorite " + (box.is_collected ? "on" : "off")} onTouchStart={this.clickCollect.bind(null, i)}>
{box.id}
</div>
</div>
},
getInitialState: function() {
return {data: [
{
id: 47,
is_collected: false
},
{
id: 23,
is_collected: false
},
{
id: 5,
is_collected: true
}
]};
},
clickCollect: function(index) {
var data = this.state.data.slice(0);
data[index].is_collected = !data[index].is_collected;
this.setState({data: data});
}
});
module.exports = ForumList;
Personally I don't always follow the rule, if you really understand what you are trying to do then I don't think it's a problem.
var data = this.state.data.slice(0);
data[index].is_collected = !data[index].is_collected;
this.setState({data: data});
In this case, mutating state and calling the setState again like this is fine
this.state.data[index].is_collected = !this.state.data[index].is_collected;
this.setState({data: this.state.data});
The reason you should avoid mutating your state is that if you have a reference to this.state.data, and calling setState multiple times, you may lose your data:
const myData = this.state.data
myData[0] = 'foo'
this.setState({ data: myData })
// do something...
// ...
const someNewData = someFunc()
this.setState({ data: someNewData })
myData[1] = 'bar' // myData is still referencing to the old state
this.setState({ data: myData }) // you lose everything of `someNewData`
If you really concerned about this, just go for immutable.js
Muting the state directly breaks the primary principle of React's data flow (which is made to be unidirectional), making your app very fragile and basically ignoring the whole component lifecycle.
So, while nothing really stops you from mutating the component state without setState({}), you would have to avoid that at all costs if you want to really take advantage of React, otherwise you would be leapfrogging one of the library's core functionalities.
If you want follow react best practices, you should do shallow copy of all your array, when you change any property. Please look into "immutable" library implementation.
But, from my experience, and from my opinion, setState method should be called if you have "shouldCompomenentUpdate" implementations. If you think, that your shallow copy will be consume much more resources, then react virtual dom checks, you can do this:
this.state.data[0].property = !this.state.data[0].property;
this.forceUpdate();
If I understood your question right, you have an array of objects and when a property of a single object in array changes,
Create a deep clone of the array and pass to setState
Create a shallow clone and pass to setState
I just checked with the redux sample todo app and in case of a single property of an object changes you've to create a fresh copy of that single object not the entire array. I recommend you to read about redux and if possible use to manage the state of your app.