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
Property 'Value' Does Not Exist on Type Eventtarget in Typescript
How to Filter by Object Property in Angularjs
What Is the Meaning of an Underscore in JavaScript Function Parameter
Why Not Always Use the Index as the Key in a Vue.Js for Loop
Jquery's Live() Is Deprecated. What Do I Use Now
How to Populate the Options of a Select Element in JavaScript
Get Index of Clicked Element Using Pure JavaScript
Downloading Canvas Element to an Image
How to Make a Div Element Editable (Like a Textarea When I Click It)
Jest: Timer and Promise Don't Work Well. (Settimeout and Async Function)
Getting How Many People Are in a Chat Room in Socket.Io
Script438: Object Doesn't Support Property or Method Ie
Reactjs Syntheticevent Stoppropagation() Only Works with React Events