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 }
})
)}
Related
I know mutating state can work against PureComponent (or similar)
Is there other reason not to mutate state?
I wonder if the 3rd way is ok to do?
// The Right Way:
// copy the existing items and add a new one
addItemImmutably = () => {
this.setState({
items: [...this.state.items, this.makeItem()]
});
};
// The Wrong Way:
// mutate items and set it back
addItemMutably = () => {
this.state.items.push(this.makeItem());
this.setState({ items: this.state.items });
};
// is this ok? (mutate but set state with new copy)
addItem3rdWay = () => {
this.state.items.push(this.makeItem());
this.setState({items: [...this.state.items]});
}
Here is an example: You have fired an async method that sends a request with your current state data. In meantime you have executed a function that mutates the state. This will cause the async function to send the mutated state despite the fact it intended to send the state before mutation.
You can think state as your database.
You don't directly mutate your database, you modify your database by API.
setState is the API.
Of course, you can directly mutate your database, but other components will have a hard time retrieving those data, because those are inconsistent right now, because somebody doesn't use the API that framework provides for you.
If you really like the way mutating state, you can use Vue, Vue is designed like that.
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 :)
My mapStateToProps function
const mapStateToProps = (state, props) => ({
object: {},
criteria: []
});
It contains two properties:
1. The object property
It get's an object from the store corresponding the URL's object id.
The resulting object looks like this:
{
object: {
id: 42,
criteria: [1, 2, 3]
}
}
2. The criteria property
This one should fetch the criteria objects from the store, with the id's of the previously fetched object's criteria ids.
I came up with the following function:
const mapStateToProps = (state, props) => ({
object: getObjectById(state, props.routeParams.objectId),
criteria: getCriteriaByIds(state, getObjectById(state, ownProps.routeParams.objectId) ? getObjectById(state, ownProps.routeParams.objectId).criteria : []),
});
The problem
There is a lot of repetition going on here. If I use criteria: getCriteriaByIds(state.props.object.criteria) directly, it seems that the object is equal to the previous state object (and thus the object can be undefined).
I'm sure there must be a simpler way to fetch the criteria without too much repetition in the code.
Note
The getObjectById and getCriteriaByIds are selectors based on the Colocating selectors with reducers course on Egghead.io.
So my question is
What is the recommended way to work with dependencies on other state
properties when working with mapStateToProps?
I don't have anything useful to add about "the recommended way to work with dependencies on other state properties when working with mapStateToProps", but I think just adding a local variable will help with the repetition a bit.
const mapStateToProps = (state, props) => {
let object = getObjectById(state, props.routeParams.objectId);
return {
object,
criteria: getCriteriaByIds(state, object ? object.criteria : [])
};
};
If you don't expect object to be undefined this could be further simplified to
const mapStateToProps = (state, props) => {
let object = getObjectById(state, props.routeParams.objectId);
return {
object,
criteria: getCriteriaByIds(state, object.criteria || [])
};
};
I would suggest not to do any kind of selection logic in mapStateToProps. That function is called EVERY time any state is changed, so your logic (getCriteriaByIds) will be called a lot.
I suggest to have objectId as top level state entry (possible with standalone reducer), so that you can just take it from state object. Consider same for critetia.
My suggestion is backed up by best practices to keep your Redux state as flat (normalized) as possible, even if you will have some repetition there
I have a function getBar() returning an object like:
{
foo: 'value',
array: ['a', 'b', 'c']
}
Here's my React component calling the above function i.e. getBar():
class foo extends Component {
state = {
bar: {}
};
componentDidMount(){
this.setState({
bar : getBar()
});
}
render() {
{this.state.bar.array.map((value, i) => <div class="row" key={i}>{value}</div>)}
}
}
It always gives me Uncaught TypeError: Cannot read property 'map' of undefined error. Exploring similar questions, I came to know I will have to declare an empty state array which I did in different ways but none worked. Can anybody please give me an appropriate answer preferably with complete logic.
I tried another way of declaring the state array to a const in render() but didn't get successful results.
Ok, so this is actually something to do with your component's lifecycle
The problem is that your render method runs before the componentDidMount method. So the first time your component renders your state looks like this:
{
bar: {},
}
So no array property on bar, which means you cannot map over it (which is why you get errors 😣). Instead you could use the componentWillMount method, to set the state before the render method runs, or you could do a check on array being set before mapping over it.
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.