I am using React with ES2015. I have this in my state:
this.state = { info: {
merchandise: {
label: '',
visible: false
},
hotels: {
label: '',
visible: false
}
}
}
I'm try to update state with this code:
this.setState({info.merchandise.label: "New Label"})
but I get an error whereas I can log the value of this.state.info.merchandise.label without any issues. What am I doing wrong?
I believe you can only use setState() on a direct property of this.state.
What you could do in this situation is
Make a shallow copy of state property you want to work with
Change the property you want changed in the copy.
The copy now has the updated properties you wanted. So assign it back to this.state
Like so:
let temp = Object.assign({}, this.state.info);
temp.merchandise.label = "New Label";
this.setState(info: temp);
You can do following,
let newState = this.state;
newState.info.merchandise.label = "New Label";
this.setState(newState);
According to React's setState doc:
Performs a shallow merge of nextState into current state
This means you should provide a valid nextState object as argument to the method. {info.merchandise.label: "New Label"} is not syntactically correct, that's why you get an error.
Treat this.state as if it were immutable.
This simply means that if you want to change some property inside the state object, you should not mutate it directly, but replace it with a new object containing the modification instead.
In your case, the state object contains several nested objects. If you want to modify a "leaf" object's property, you'll also have to provide new objects for every containing object up the tree (because each of them have a property that changes in the process). Using Object.assign() to create new objects, you could write:
// new "leaf" object:
const newMerchandise = Object.assign({}, this.state.info.merchandise,
{label: "New Label"});
// new "parent" object:
const newInfo = Object.assign({}, this.state.info,
{merchandise: newMerchandise});
// new state ("parent") object:
const newState = Object.assign({}, this.state,
{info: newInfo});
// eventually:
this.setState(newState);
By the way, you'll notice that it's easier to handle the state object when its structure tends to be "flat" rather than "deeply nested".
Related
So, I have a checkbox having onchange method but in the method, I am not able to change the value.
Also, the value received is the same as key in the state variable.
Following the code:
constructor(props){
super(props)
this.state={pins:[],filter:{Irrigation:true,Patvan:true,Drinking:true,Minigrid:true,Rooftop:true}}
this.handleFilterChange=this.handleFilterChange.bind(this)
}
handleFilterChange(filtervalue){
console.log(filtervalue) //lets assume value to be Drinking
console.log(this.state.filter[filtervalue]) // it says true
this.setState({filter[filtervalue]:!this.state.filter[filtervalue]}) //here the error occurs
}
I know i am doing some syntax mistake. Help in pointing it out. Thanks
You can't update the state using filter[filtervalue] as property key, you should do something like this:
this.setState(
prevState => ({
...prevState,
filter: {
...prevState.filter,
[filtervalue]: !prevState.filter[filtervalue]
}
})
)
First, if you need to access the previous state during an update of the state, you should use the method tecnique, not the object one.
Then, updating an inner property of the state can be a bit cumbersome as you see, my suggestion is to use something like immer.
First of all when your state changes depend on the current state use the updater method of setState. After that since you are updating a nested object you need to recreate the nesting up to the property you need to update.
In your case the object passed to setState should be
{
filter: {
Irrigation: true,
Patvan: true,
Drinking: false, // this is what changed
Minigrid: true,
Rooftop: true
}
}
So your actual setState call should be
this.setState(({
filter // destructure the original state and extract the filter property
}) => ({
filter: {
...filter, // spread the existing filter properties
[filtervalue]: !filter[filtervalue] // overwrite the property that changed
}
}))
Let's say, I have a state that looks as follows.
constructor(props) {
super(props);
this.state = {
setting_a: "value-1",
setting_b: "color-green"
}
}
When I change the state of a specific setting (e.g. setting_a), I don't want other settings (e.g. setting_b) to disappear. So I also specify the other settings while changing the state. (which is easy using the spread operator ...state).
this.setState( {...this.state, setting_a: "value-2"});
I noticed though, that some tutorials restate them, and others only specify the changed key-values.
Things got just a little bit more complicated since the introduction of the Component#getDerivedStateFromProps method, ( since React 16.3 ).
static getDerivedStateFromProps(props, state) {
const oldSetting = state.setting_a;
const newSetting = props.setting_a;
if (oldSetting !== newSetting) {
// this is a very similar situation.
return ({ ...state, state.setting_a: props.setting_a});
}
return null;
}
Again, in the above example, I add all previous settings (i.e. ...state), because I don't want the other settings to be removed.
In both these cases, the same question: do I need to specifically repeat values which are already in the state ? Or are the states always merged incrementally, without removing ?
You don't need to copy the state (using spread operator or any idea) when updating the state with setState. The setState method updates the required state only:
this.setState( {setting_a: "value-2"});
So, now you will still get:
state = {
setting_a: "value-2",
setting_b: "color-green"
}
Similarly, it works like that when you return the object in getDerivedStateFromProps. The returned value is applied in the state without mutation.
You only need to copy the state when you want to update the property of state. For eg.:
// initial state
this.state = {
settings: {
a: 'value-1',
b: 'color-green'
}
}
Now, we have a and b property in settings state. So now, if you wanted to update the a, then you'll need to copy the settings:
this.setState((state) => ({settings: {...state.settings, a: 'value-2' } }))
The preceding example is with settings object. You can think similar with array of state. You may just do a google search for how to update the array without mutation?
It depends.
In your first case you could do:
this.setState( prevState => {
prevState.setting_a = "value-2";
return prevState
});
Or just go with:
this.setState({ setting_a: "value-2" });
As per React Docs State Updates are Merged.
I know how To avoid mutating objects and arrays in react state, but I am not sure about variables that are not objects and arrays like this example
this.state = {
cubeNumber: 0,
}
onNumberChange = (event) => {
this.setState({ [event.target.name]: event.target.value })
}
cubeArrayRender = () => {
let { cubeNumber } = this.state;
am I mutating state by using parseInt like this?
let cubes = parseInt(cubeNumber, 10);
or if I write like this?
let cubes = cubeNumber;
cubes = 2;
If I am mutating state, how can I avoid it?
According to React documentation you shouldn't assign state like this
// Wrong
this.state.comment = 'Hello';
But only this:
// Correct
this.setState({comment: 'Hello'});
So yeah you are not mutating state in your code :)
You can mutate state only acting directly on this.state or through setState.
You are not mutating state (or anything) by using the method you have shown in your example. parseInt returns an Integer and does not modify the String/Number that you give it, instead creating a new instance.
As per #Steve Vaughan answer. In this case you are not mutating state because the state
you used for this test is value type. But object are reference type. While you assigning
the cubeNumber to new variable cube that makes the deep copy. But that's not same for objects, you are just passing reference.
For example:
//intial state
this.state = {
user: {name:"sarath"},
age:25
}
let age=this.state.age; //deep copy
age=35; // this not going affect the original state
let user=this.state.user //Shallow copy
user.name="kumar" //It mutates the original object also
output:
original age: 25
new age: 35
name: Kumar
new name: Kumar
you can avoid the mutation by using two methods
Object Assign
This method is used to copy the values of all enumerable own properties from one or more source objects to a target object.
let user= Object.Assign({},this.state.user); //deep copy of values
user.name="kumar"; // Here the original state is untouched.
Deep cloning of entire object
Object.Assign copies property values only. It won't copy the reference inside of the Object.
For example:
//intial state
this.state = {
user: {name:"sarath",interest:{music:"western",sports:"foot ball"},
age:25
}
let user=Object.Assign({},this.state.user);
user.name="Jasrin";
user.interest.sports="cricket";
console.log(this.state.user.name); //sarath
console.log(user.name); //kumar
console.log(this.state.user.interest.sports); //cricket
console.log(this.state.user.interest.sports); //cricket
Alternate way to do deep cloning is
let user=JSON.parse(JSON.stringify(this.state.user))
I am dealing with Proposals and locations.
A location can have multiple proposals.
The component is passed a location object (below as passedProps) to show all the proposals for this location :
<Proposals location={ location } key={location.id}/>
Here is are my redux props :
const mapStateToProps = (state , passedProps) => {
return {
proposals : state.propertyProposals[passedProps.location.id]
};
};
when adding a new proposal, I want to store their ids by location, so in a addition to storing the proposal, I am storing their Ids, in an array keyed by location Id.
The first proposal added and refreshed with new props just fine, however even though the second one is successfully pushed to the array, this is not triggering new props, so it does not "refresh" -- If I leave the route and come back I see the new 2nd proposal (which did not show the first time)
Here is the PROPOSAL_CREATE action for a new Proposal.
type :'PROPOSAL_CREATE',
payload :
{'e7ef104e-19ed-acc8-7db5-8f13839faae3' : {
id : 'e7ef104e-19ed-acc8-7db5-8f13839faae3',
locationId: '41e9c5d8-a520-7e3b-939a-12f784d49712'
}
}
here is the case which handles it :
case 'PROPOSAL_CREATE':
{
const proposal = Object.values(action.payload)[0];
const locationId = proposal.locationId;
let newState = {...state}
if (locationId in newState) {
newState[locationId].push(proposal.id)
} else {
newState[locationId] = [proposal.id]
}
return newState
}
Is there an obvious reason I am not seeing the change in the component for the second entry?
Thanks
There is one issue here. Your store state is not immutable. You have used below line to make a copy:
let newState = {...state}
Here it does make copy of object but it's shallow copy, hence your array object in newState and state have the same reference. That's why redux doesn't identify the change in store and hence props are not updated in sequence.
You can clone your state by below methods:
let newState = JSON.parse(JSON.stringify(state));
OR if you use jQuery then:
let newState = $.extend(true, {}, state);
I think this will surely fix your issue.
Based on your reducer logic i think that you did not specify action type.
The one of the redux conventions is the action recognition based on type property.
I bet that you forgot to specify that property.
var properAction = {
type: 'PROPOSAL_CREATE',
payload: {
{
'e7ef104e-19ed-acc8-7db5-8f13839faae3': {
id: 'e7ef104e-19ed-acc8-7db5-8f13839faae3',
locationId: '41e9c5d8-a520-7e3b-939a-12f784d49712'
}
}
}
I would recommend you to write action creators it will reduce your place for typos like that.
Cheers!
2 things:
I forgot that Arrays of an original object are still by reference. So even after
let newState = {...state}
newState[locationId]
has the same reference as
state[locationId]
As a result my original statement was mutating the original state, not creating a newState
and so
newState[locationId].push(proposal.id)
needed to be
newState[locationId] = state[locationId].concat(proposal.id);
or es6
newState[locationId] = [ ...state[locationId] , proposal.id ] ;
I am trying to add an array to a state that contains an array. I wish to add the data to the end of the array. My data looks like this:
getInitialState: function() {
return {name: [{program: "File Manager"},
{program: "Add File"},
{program: "Index"},
{program: "File Editor"}],
program: "Index",
display: true};
},
and I wish to update the name variable to also contain something. For example add {program: "Text Editor"}
How could I go about doing this with this.setState()? Or, if not, is there another way to do this. I read about $push but couldn't really find any examples that helped me out. I wish to do this in componentDidMount:
Create a new array and assign it to the state:
var currentName = this.state.name;
var newName = currentName.concat([{program: "Text Editor"}]);
this.setState({name: newName});
(The first line isn't necessary, just included for clarity.)
You could also use the function version of setState:
this.setState(function(state) {
return { name: state.name.concat([{program: "Text Editor"}]) };
});
If you're in an environment that supports ES2015 arrows, you can do this in a pretty concise way:
this.setState(state => ({ name: state.name.concat([{program: "Text Editor"}]) });
Since concat already generates a new array, you don't need to use the immutability helpers, but if you want to, it'd look like this:
var newState = update(this.state, { // generate a new state object...
name: { $push: [ "TextEditor" ] } // with the item pushed onto the `name` key
});
It's possible to mutate an existing property of this.state and then re-set it with setState, e.g.
// warning: antipattern!
this.state.name.push("Text Editor");
this.setState({name: this.state.name});
but this is not considered a very good practice (you should always avoid directly modifying this.state):
Notes:
NEVER mutate this.state directly, as calling setState() afterwards may replace the mutation you made. Treat this.state as if it were immutable.
You can read the this.state.name array, modifying it (by pushing to the array), then call setState again.