Can't update the specific key of state variable - reactjs

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

Related

setState override existing state

I have the following function to set state:
const [study, setStudy] = useState(defaultState)
const setValue = (section, key, value) => {
setStudy({...study, [section]: {...study[section], [key]: value}})
}
But for some reason it keeps overriding the existing state, what am I doing wrong?
I call the function like this:
setValue('company', 'name', 'test')
setValue('property', 'state', 'test2')
setValue('property', 'address', 'test3')
Structure of data:
const defaultState: StudyData = {
client: {},
company: {},
property: {},
study: {}
}
I think the problem is that the study value you destruct is still an old version (a memoized one). It is the same between all setValue calls and thus your first two setValue calls do essentially nothing. If this is what's happening to you, the best way to fix it would be to give a callback function to setStudy. Any function given to a setter will get the absolute latest version of the state as parameter. Try it like this:
setStudy((latestStudy) => {
return {
...latestStudy,
[section]: {...latestStudy[section], [key]: value}
}
});
When dealing with multiple state-updating events, the standard behavior for React is to batch them. The state-updating method does not immediately update the state of the component, React just puts the update in queue to be processed later when event handling is complete. Changes are all flushed together at the end of the event and you don't see the intermediate state.
With batching, when the final set-state event executes, it has no recollection that the prevState has been updated (asynchrounous), which is why the final state appears to only reflect changes made by the last event.
Using a call-back function as the first argument for your setStudy method will help you return the intermediate states and avoid the issue of batching. The updater function will loop through and shallowly merge all updated states with the previous component state. This ensures that we receive all state-updates and use all intermediate states before ultimately arriving at the final update.
const setValue = (section, key, value) => {
setStudy(study => {
return {
...study,
[section]: { ...study[section], [key]: value }
};
});
};
Also see working sandbox: https://codesandbox.io/s/delicate-tree-vqrz7

Is it ok to modify part of prevState inside setState?

Is there a real disadvantage to modifying part of prevState and returning that part inside setState() ?
Example:
this.setState(prevState => {
prevState.myObject.isItTrue = !prevState.myObject.isItTrue;
return {myObject: prevState.myObject};
});
Rather than:
this.setState(prevState => {
const myObject = Object.assign({}, prevState.myObject);
myObject.isItTrue = !myObject.isItTrue;
return {myObject: myObject};
});
Is there any real disadvantage to the first code where I save myself the Object.assign() ?
EDIT: If I am correct, prevState.myObject is simply a reference to this.state.myObject, so changing prevState.myObject actually changes this.myObject.object as well! However, this doesn't seem to break anything, as long as I use setState() to pass an object that contains the new data, even if it's just a reference to the old objects inside this.state.
Do you agree that this is still ok, i.e. it won't break anything to do it like this?
Following documentation:
state is a reference to the component state at the time the change is being applied. It should not be directly mutated. Instead, changes should be represented by building a new object based on the input from state and props.
https://reactjs.org/docs/react-component.html
So you should not apply changes directly to that state.
Either way, why not do something like this?:
this.setState(prevState => ({
myObject : {
...prevState.myObject,
isItTrue: !prevState.myObject.isItTrue,
}
}));
This way will get all the elements from the prevState but also change all the ones you want to modify.
First prevState and the this.state are the same object. So you are modifying the actual state directly.
Secondly, down the road you might pass the myObject as a prop to another component, and since it will be the same object always that component will not know that something has changed and it will not re-render (for example PureComponent and ones that implement componentDidUpdate or shouldComponentUpdate and test for changes)
See https://codesandbox.io/s/zen-aryabhata-m07l4 for showcases of all issues.
So you should use
this.setState(state => ({
myObject: {
...state.myObject,
isItTrue: !state.myObject.isItTrue
}
}));

Modify Child state from parent just once

I have created a <Form> Component that makes all the form-fields you give to it controlled by injecting value and onChange props to the form-fields by iterating through them. This has been working perfectly well for me for most of the forms I have created.
When I needed to have the functionality of the form-field values controlling some aspect of the parent state, I added a onFormValueChange prop to the Form that would get called whenever a field value gets updated. Using this I can track a subset of the changes to the Form's state.
However, now my problem is this...how do I override the value of a form-field, conditional on some event that occurs in the parent. I have not been able to figure out how to override the Form's state just once. If I give it a prop that sets an override value like {name: string, value: any}, then on every update this override value will override the form-field's value which is not good.
These are the solutions I thought of but they seem extremely hacky and I was hoping someone in the SO community can help.
Set an override prop on the Form Component which times out after around 100ms and hope that the user doesn't try to modify the form in that tiny duration. But I dislike using setTimeout for hacks like these.
Pass a disableOverride function along with the overrideValue prop. Then in my Form's shouldComponentUpdate I can just call disableOverride() in the callback of the setState I will use to override the value. Something like:
shouldComponentUpdate(nextProps, nextState) {
if (nextProps.override) {
const { name, value } = nextProps.override;
const newState = Object.assign({}, this.state, { [name]: value });
this.setState(newState, () => {
nextProps.disableOverride();
});
return false;
}
return true;
}
But this also feels unnecessarily complicated, and possibly quite vulnerable to crashing unexpectedly.
EDIT Some further clarification: The point of this would be so that for example if I have 'country' and 'city' fields then if 'country' is cleared, then the 'city' should get cleared too. I can track the state of 'country' with onFormValueChange but don't have an API to modify the Form state in order to clear the 'city' field.
I came up with a solution. It was absurdly simple, I dont know why it took me so long.
componentDidUpdate(prevProps, prevState) {
if (!isEqual(prevProps.override, this.props.override)) {
const { name, value } = nextProps.override;
this.setState({
[name]: value
});
}
}
isEqual is an object comparison function taken from lodash

When changing one specific state setting, is it necessary to restate all the other ones?

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.

React Component state value not updated, when setState is called on jsonObject's property

I am working on three properties of JSON object which returns boolean values.
updateChange=(value) =>{
//Making copy of existing json Object
let newState = JSON.parse(JSON.stringify(this.state.mainValue));
// chaning property of cellular over here
newState.cellular = value;
console.log(newState);
this.setState({mainValue:newState});
//I tried setState in many different ways. But Component is not changing state value
console.log(this.state.mainValue)
};
I found why this is happening. I am using getDerivedStateFromProps in this component to look at changes made by parent to update values. This is avoiding changes made to state of the child. So I have created a state called previous state to compare the previous props and present props from parent to render. This avoids component to refresh when ever it local state values changes.
static getDerivedStateFromProps(propsnow,state){
if(state.previous !== propsnow.detailSwitches){
return{
previous :propsnow.detailSwitches,
cellular: propsnow.detailSwitches.cellular,
wifi1: propsnow.detailSwitches.wifi1,
wifi2: propsnow.detailSwitches.wifi2
};
}
return null;
}
Any examples or better practices can be helpful. Thanks
You are ONLY setting the 'mainValue' variable of your state to the value of newstate, shouldn't you be updating the whole state?
this.setState({...newState})
You can try this
this.setState(prevState => ({
mainValue: {
...prevState.mainValue,
cellular: value
}}))
It is an immutable way to update state.

Resources