This.Setstate Isn't Merging States as I Would Expect

this.setState isn't merging states as I would expect

I think setState() doesn't do recursive merge.

You can use the value of the current state this.state.selected to construct a new state and then call setState() on that:

var newSelected = _.extend({}, this.state.selected);
newSelected.name = 'Barfoo';
this.setState({ selected: newSelected });

I've used function _.extend() function (from underscore.js library) here to prevent modification to the existing selected part of the state by creating a shallow copy of it.

Another solution would be to write setStateRecursively() which does recursive merge on a new state and then calls replaceState() with it:

setStateRecursively: function(stateUpdate, callback) {
var newState = mergeStateRecursively(this.state, stateUpdate);
this.replaceState(newState, callback);
}

setState not merging but replacing

If you want to set only background properly in theme, you need to use spread syntax with theme and not state like

this.setState(prevState => ({
theme: { ...prevState.theme, background: { bgType: 'colour', value: color.hex } }
}));

In case you want to merge the background values too, you need to spread it too like

this.setState(prevState => ({
theme: { ...prevState.theme, background: {
...prevState.theme.background,
bgType: 'colour', value: color.hex
}}
}));

React: Updating one state property removes other states properties in the state

The behaviour is correct.

   e.g: var obj = {a:1,b:3};
obj = {a:4};//Obj updated. Now obj.b will be undefined.This is what you worried for.

In your case, you can do something like this. It is not one of the best solution.

onSubmit(e)
{
e.preventDefault();
let state = this.state.translation;
state.en.content = 'content';
this.setState(state);

console.log(this.state);
}

Updating and merging state object using React useState() hook

Both options are valid, but just as with setState in a class component you need to be careful when updating state derived from something that already is in state.

If you e.g. update a count twice in a row, it will not work as expected if you don't use the function version of updating the state.

const { useState } = React;
function App() { const [count, setCount] = useState(0);
function brokenIncrement() { setCount(count + 1); setCount(count + 1); }
function increment() { setCount(count => count + 1); setCount(count => count + 1); }
return ( <div> <div>{count}</div> <button onClick={brokenIncrement}>Broken increment</button> <button onClick={increment}>Increment</button> </div> );}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://unpkg.com/react@16/umd/react.development.js"></script><script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id="root"></div>

Update nested array in this.setState

You will have to grab the item from the array based on the index, and then add the file to the item that was just grabbed.

index is just a number and you are applying a spread operator against it which is the reason for the error.

reader.onloadend = () => {
// clone the array from state so that it is not mutated directly
let clonedGroups = [...groups];
let clonedPreview = [...preview];
// grab the corresponding item based on the index
let groupItem = clonedGroups[index] || [];
let previewItem = clonedPreview[index] || [];

// Update the corresponding item
groupItem = [...groupItem, file];
previewItem = [...previewItem, reader.result];

// set the state with updated array objects
this.setState({
data: {
...data,
groups: clonedGroups
},
preview: clonedPreview
})
}

How can I update state.item[1] in state using setState?

Here's how you can do it without helper libs:

handleChange: function (e) {
// 1. Make a shallow copy of the items
let items = [...this.state.items];
// 2. Make a shallow copy of the item you want to mutate
let item = {...items[1]};
// 3. Replace the property you're intested in
item.name = 'newName';
// 4. Put it back into our array. N.B. we *are* mutating the array here,
// but that's why we made a copy first
items[1] = item;
// 5. Set the state to our new copy
this.setState({items});
},

You can combine steps 2 and 3 if you want:

let item = {
...items[1],
name: 'newName'
}

Or you can do the whole thing in one line:

this.setState(({items}) => ({
items: [
...items.slice(0,1),
{
...items[1],
name: 'newName',
},
...items.slice(2)
]
}));

Note: I made items an array. OP used an object. However, the concepts are the same.


You can see what's going on in your terminal/console:

❯ node
> items = [{name:'foo'},{name:'bar'},{name:'baz'}]
[ { name: 'foo' }, { name: 'bar' }, { name: 'baz' } ]
> clone = [...items]
[ { name: 'foo' }, { name: 'bar' }, { name: 'baz' } ]
> item1 = {...clone[1]}
{ name: 'bar' }
> item1.name = 'bacon'
'bacon'
> clone[1] = item1
{ name: 'bacon' }
> clone
[ { name: 'foo' }, { name: 'bacon' }, { name: 'baz' } ]
> items
[ { name: 'foo' }, { name: 'bar' }, { name: 'baz' } ] // good! we didn't mutate `items`
> items === clone
false // these are different objects
> items[0] === clone[0]
true // we don't need to clone items 0 and 2 because we're not mutating them (efficiency gains!)
> items[1] === clone[1]
false // this guy we copied

Setting state correctly using the State Hook

But isn't this the "wrong" solution because it relies directly on the state?

setState merges the new state into the old one. And does it in a 'piecemeal' fashion e.g. new state can contain a small subset of old state's data. So it's beneficial to have the old state at hand inside the setState body and explicitly merge it using
this.setState(oldState => ({ ...oldState, someProp: true }) )

'Explicitly' is better because it shows you don't have illusions about what setState will do anyway: merging even if you didn't.

useState replaces the old state with the new one so the need to have access to oldState is less acute. Even though I wouldn't mind it.

As for updated asynchronously, both setState and useState can batch updates. Although useState won't do batching when dealing with pending Promises, as opposed to ready-to-use values like count + 1 from your sample.

Coming back to setState, there is another reason why the flavor setState(oldState => ({...}) is better. Remember, setState always do merging no matter what. Combined with batching, it can lead to the following piece code executed by React if the flavor setState(newValue) was used:

Object.assign(oldState, newValue1, newValue2, newValue3)

where the values newValueX are the batched updates. Object.assign ensures that 'last update wins'. If the series of newValueX represents successive attempts to increment the same counter then it might not work as expected. Therefore the other flavor of setState is better. With useState and batching it looks like this kind of danger persists.



Related Topics



Leave a reply



Submit