this.setState isn't merging states as I would expect - javascript
I have the following state:
this.setState({ selected: { id: 1, name: 'Foobar' } });
Then I update the state:
this.setState({ selected: { name: 'Barfoo' }});
Since setState is suppose to merge I would expect it to be:
{ selected: { id: 1, name: 'Barfoo' } };
But instead it eats the id and the state is:
{ selected: { name: 'Barfoo' } };
Is this expected behavior and what's the solution to update only one property of a nested state object?
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);
}
Immutability helpers were recently added to React.addons, so with that, you can now do something like:
var newState = React.addons.update(this.state, {
selected: {
name: { $set: 'Barfoo' }
}
});
this.setState(newState);
Immutability helpers documentation.
Since many of the answers use the current state as a basis for merging in new data, I wanted to point out that this can break. State changes are queued, and do not immediately modify a component's state object. Referencing state data before the queue has been processed will therefore give you stale data that does not reflect the pending changes you made in setState. From the docs:
setState() does not immediately mutate this.state but creates a pending state transition. Accessing this.state after calling this method can potentially return the existing value.
This means using "current" state as a reference in subsequent calls to setState is unreliable. For example:
First call to setState, queuing a change to state object
Second call to setState. Your state uses nested objects, so you want to perform a merge. Before calling setState, you get current state object. This object does not reflect queued changes made in first call to setState, above, because it's still the original state, which should now be considered "stale".
Perform merge. Result is original "stale" state plus new data you just set, changes from initial setState call are not reflected. Your setState call queues this second change.
React processes queue. First setState call is processed, updating state. Second setState call is processed, updating state. The second setState's object has now replaced the first, and since the data you had when making that call was stale, the modified stale data from this second call has clobbered the changes made in the first call, which are lost.
When queue is empty, React determines whether to render etc. At this point you will render the changes made in the second setState call, and it will be as though the first setState call never happened.
If you need to use the current state (e.g. to merge data into a nested object), setState alternatively accepts a function as an argument instead of an object; the function is called after any previous updates to state, and passes the state as an argument -- so this can be used to make atomic changes guaranteed to respect previous changes.
I didn't want to install another library so here's yet another solution.
Instead of:
this.setState({ selected: { name: 'Barfoo' }});
Do this instead:
var newSelected = Object.assign({}, this.state.selected);
newSelected.name = 'Barfoo';
this.setState({ selected: newSelected });
Or, thanks to #icc97 in the comments, even more succinctly but arguably less readable:
this.setState({ selected: Object.assign({}, this.state.selected, { name: "Barfoo" }) });
Also, to be clear, this answer doesn't violate any of the concerns that #bgannonpl mentioned above.
Preserving the previous state based on #bgannonpl answer:
Lodash example:
this.setState((previousState) => _.merge({}, previousState, { selected: { name: "Barfood"} }));
To check that it's worked properly, you can use the second parameter function callback:
this.setState((previousState) => _.merge({}, previousState, { selected: { name: "Barfood"} }), () => alert(this.state.selected));
I used merge because extend discards the other properties otherwise.
React Immutability example:
import update from "react-addons-update";
this.setState((previousState) => update(previousState, {
selected:
{
name: {$set: "Barfood"}
}
});
As of right now,
If the next state depends on the previous state, we recommend using
the updater function form, instead:
according to documentation https://reactjs.org/docs/react-component.html#setstate, using:
this.setState((prevState) => {
return {quantity: prevState.quantity + 1};
});
My solution for this kind of situation is to use, like another answer pointed out, the Immutability helpers.
Since setting the state in depth is a common situation, I've created the folowing mixin:
var SeStateInDepthMixin = {
setStateInDepth: function(updatePath) {
this.setState(React.addons.update(this.state, updatePath););
}
};
This mixin is included in most of my components and I generally do not use setState directly anymore.
With this mixin, all you need to do in order to achieve the desired effect is to call the function setStateinDepth in the following way:
setStateInDepth({ selected: { name: { $set: 'Barfoo' }}})
For more information:
On how mixins work in React, see the official documentation
On the syntax of the parameter passed to setStateinDepth see the Immutability Helpers documentation.
I am using es6 classes, and I ended up with several complex objects on my top state and was trying to make my main component more modular, so i created a simple class wrapper to keep the state on the top component but allow for more local logic.
The wrapper class takes a function as its constructor that sets a property on the main component state.
export default class StateWrapper {
constructor(setState, initialProps = []) {
this.setState = props => {
this.state = {...this.state, ...props}
setState(this.state)
}
this.props = initialProps
}
render() {
return(<div>render() not defined</div>)
}
component = props => {
this.props = {...this.props, ...props}
return this.render()
}
}
Then for each complex property on the top state, i create one StateWrapped class. You can set the default props in the constructor here and they will be set when the class is initialised, you can refer to the local state for values and set the local state, refer to local functions, and have it passed up the chain:
class WrappedFoo extends StateWrapper {
constructor(...props) {
super(...props)
this.state = {foo: "bar"}
}
render = () => <div onClick={this.props.onClick||this.onClick}>{this.state.foo}</div>
onClick = () => this.setState({foo: "baz"})
}
So then my top level component just needs the constructor to set each class to it's top level state property, a simple render, and any functions that communicate cross-component.
class TopComponent extends React.Component {
constructor(...props) {
super(...props)
this.foo = new WrappedFoo(
props => this.setState({
fooProps: props
})
)
this.foo2 = new WrappedFoo(
props => this.setState({
foo2Props: props
})
)
this.state = {
fooProps: this.foo.state,
foo2Props: this.foo.state,
}
}
render() {
return(
<div>
<this.foo.component onClick={this.onClickFoo} />
<this.foo2.component />
</div>
)
}
onClickFoo = () => this.foo2.setState({foo: "foo changed foo2!"})
}
Seems to work quite well for my purposes, bear in mind though you can't change the state of the properties you assign to wrapped components at the top level component as each wrapped component is tracking its own state but updating the state on the top component each time it changes.
Solution
Edit: This solution used to use spread syntax. The goal was make an object without any references to prevState, so that prevState wouldn't be modified. But in my usage, prevState appeared to be modified sometimes. So, for perfect cloning without side effects, we now convert prevState to JSON, and then back again. (Inspiration to use JSON came from MDN.)
Remember:
State updates are shallow: they only go one level deep
State shouldn't be directly mutated. Instead, use setState(prevState => stateChange)
Steps
Make a copy of the root-level property of state that you want to change
Mutate this new object
Create an update object
Return the update
Steps 3 and 4 can be combined on one line.
Example
this.setState(prevState => {
var newSelected = JSON.parse(JSON.stringify(prevState.selected)) //1
newSelected.name = 'Barfoo'; //2
var update = { selected: newSelected }; //3
return update; //4
});
Simplified example:
this.setState(prevState => {
var selected = JSON.parse(JSON.stringify(prevState.selected)) //1
selected.name = 'Barfoo'; //2
return { selected }; //3, 4
});
This follows the React guidelines nicely. Based on eicksl's answer to a similar question.
ES6 solution
We set the state initially
this.setState({ selected: { id: 1, name: 'Foobar' } });
//this.state: { selected: { id: 1, name: 'Foobar' } }
We are changeing a property on some level of the state object:
const { selected: _selected } = this.state
const selected = { ..._selected, name: 'Barfoo' }
this.setState({selected})
//this.state: { selected: { id: 1, name: 'Barfoo' } }
React state doesn't perform the recursive merge in setState while expects that there won't be in-place state member updates at the same time. You either have to copy enclosed objects/arrays yourself (with array.slice or Object.assign) or use the dedicated library.
Like this one. NestedLink directly supports handling of the compound React state.
this.linkAt( 'selected' ).at( 'name' ).set( 'Barfoo' );
Also, the link to the selected or selected.name can be passed everywhere as a single prop and modified there with set.
have you set the initial state?
I'll use some of my own code for example:
getInitialState: function () {
return {
dragPosition: {
top : 0,
left : 0
},
editValue : "",
dragging : false,
editing : false
};
}
In an app I'm working on, this is how I've been setting and using state. I believe on setState you can then just edit whatever states you want individually I've been calling it like so:
onChange: function (event) {
event.preventDefault();
event.stopPropagation();
this.setState({editValue: event.target.value});
},
Keep in mind you have to set the state within the React.createClass function that you called getInitialState
I use the tmp var to change.
changeTheme(v) {
let tmp = this.state.tableData
tmp.theme = v
this.setState({
tableData : tmp
})
}
Related
post setState evaluation of state
In a react component, I update state in various ways, but I would like to do an evaluation (call a function) after the state was updated. When I do the following, secondUpdate() does not access to the updated state, so it is one cycle late: firstUpdate = e => { this.setState({ email: e.state.value }); // ... some validation secondUpdate(); } secondUpdate() { const allValid= this.state.aIsValid & this.state.bIsValid & this.state.cIsValid; this.setState({ allIsValid: allValid }); } How should I bind secondUpdate() to any or some state update?
You can use setState callback : this.setState({ email: e.state.value } , () => { // Will get called once the state is updated // ... some valdidations secondUpdate(); });
allIsValid shall not be a state at all. By having states that depend on each other, you risk that states get out of sync, and your logic breaks. Instead, as allIsValid is derived from other states, it can just be calculated based on the state inside render: render() { const { email } = this.state; const allIsValid = email.length > 5 && /*...*/; // ... }
You can use componentDidUpdate(). componentDidUpdate(prevProps, prevState){ // code from secondUpdate() } Here is official doc
You might like to go through this: https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous Because this.props and this.state may be updated asynchronously, you should not rely on their values for calculating the next state. To fix it, use a second form of setState() that accepts a function rather than an object. That function will receive the previous state as the first argument, and the props at the time the update is applied as the second argument: this.setState({ // change the state }, () => { //Perform something after the state is updated. })
Spread operator in React .setState()
Given the following snippet extracted from a React class component: constructor(props) { super(props) this.state = { active: true } } deactivate = () => { this.setState({ ...this.state, active: false }) } What is the aim of the spread operator into the stopCounter() method? The application also works removing it: deactivate = () => { this.setState({ active: false }) }
Both works in that case, but you don't need to use that. Just setting the state will be okay: this.setState({active: false}) But let me explain what if you have nested level of states like: state = { foo: { a: 1, b: 2, c: 3 } } And when you need to update the foo's c state only, then you'll need to merge the state like: this.setState({ foo: { ...this.state.foo, c: 'updated value' }}) So, the spread syntax merges object with later object. It's similar to Object.assign.
The second snippet works because React is implicitly doing the spreading for you. Per React's documentation for setState: You may [...] pass an object as the first argument to setState(): setState(stateChange[, callback]). This performs a shallow merge of stateChange into the new state.
The goal is just set active to false and keep the rest with no modifications. You can edit a part of your state just passing the required names, and skipping the rest.
Mutating nested object/arrays as states in React [duplicate]
This question already has answers here: Whats the best way to update an object in an array in ReactJS? (4 answers) Closed 4 years ago. I am using React components which look like this (a simplified version of the components I used, below). My question is: how to make the same but using this.setState? The below code works, but I am mutating the state directly, and I am receiving the following warning: Do not mutate state directly. Use setState() class App extends Component { constructor(props) { super(props); this.state = { playerState: [ { name: 'Jack', hp: 30 },{ name: 'Alan', hp: 28 } ], }; } lowerPlayerHealth = (index) => () => { this.state.playerState[index].hp = this.state.playerState[index].hp - 1 this.forceUpdate(); } render() { return ( <div className="App"> <p>Player 1: {this.state.playerState[0].name}</p> <p>Health: {this.state.playerState[0].hp}</p> <button onClick={this.lowerPlayerHealth(0)}>Hit player 1</button> <p>Player 2: {this.state.playerState[1].name}</p> <p>Health: {this.state.playerState[1].hp}</p> <button onClick={this.lowerPlayerHealth(1)}>Hit player 2</button> </div> ); } } When rendered, it looks like this:
If you want to modify an existing value in the state, you should never get the value directly from the state and update the state object, but rather use the updater function in setState so you can guarantee the state values are the ones you need at the time of updating the state. This is just how React's state works and it's a very common React mistake. From the official docs setState() does not always immediately update the component. It may batch or defer the update until later. This makes reading this.state right after calling setState() a potential pitfall. Instead, use componentDidUpdate or a setState callback (setState(updater, callback)), either of which are guaranteed to fire after the update has been applied. If you need to set the state based on the previous state, read about the updater argument below. setState() will always lead to a re-render unless shouldComponentUpdate() returns false. If mutable objects are being used and conditional rendering logic cannot be implemented in shouldComponentUpdate(), calling setState() only when the new state differs from the previous state will avoid unnecessary re-renders. The first argument is an updater function with the signature: (state, props) => stateChange 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. Both state and props received by the updater function are guaranteed to be up-to-date. The output of the updater is shallowly merged with state. So you must get the value exactly when you want to update the component inside the setState function using the first argument of the updater function. lowerPlayerHealth = (index) => () => { // use setState rather than mutating the state directly this.setState((state, props) => { // state here is the current state. Use it to update the current value found in state and ensure that it will be set correctly return (state); // update state ensuring the correct values }); } Solution To lower a value found in state: class App extends React.Component { constructor(props){ super(props); this.state = { playerState: [ { name: 'Jack', hp: 30 }, { name: 'Alan', hp: 28 } ], }; } lowerPlayerHealth = (index) => () => { this.setState((state, props) => { state.playerState[index].hp -=1; //update the current value found in state return (state); // update state ensuring the correct values }); } render() { return ( <div className="App"> <p>Player 1: {this.state.playerState[0].name}</p> <p>Health: {this.state.playerState[0].hp}</p> <button onClick={this.lowerPlayerHealth(0)}>Hit player 1</button> <p>Player 2: {this.state.playerState[1].name}</p> <p>Health: {this.state.playerState[1].hp}</p> <button onClick={this.lowerPlayerHealth(1)}>Hit player 2</button> </div> ); } }
You've answered your own question: don't mutate state. Also, best practice suggests using the function version of setState. Since playerState is an array, use Array.map to create a new array containing the same objects, replacing only the one you want to change: lowerPlayerHealth = (indexToUpdate) => () => { this.setState(state => ({ ...state, playerState: state.playerState.map( (item, index) => index === indexToUpdate ? { ...item, hp: item.hp - 1 } : oldItem ) })); } If you made playerState an object instead of an array, you can make it tidier: lowerPlayerHealth = (indexToUpdate) => () => { this.setState(state => ({ ...state, playerState: { ...state.playerState, [indexToUpdate]: { ...state.playerState[indexToUpdate], hp: state.playerState[idToindexToUpdatepdate].hp - 1 } } })); }
Changing a child components state changes the parent components props
Parent component is a header Child component is a form which is used to change values appearing in the header after a save which fires a redux action. I set the child state with constructor(props) { super(props); this.state = { object: { ...props.object }, hidden: props.hidden, }; } The form is used to render the state.object and modify the state.object. When I modify state.object, the props from the parent component change as well. handleObjectChange = (event, key, subkey) => { console.log('props', this.props.object.params); console.log('state', this.state.object.params); event.preventDefault(); const value = this.handlePeriod(event.target.value); this.setState((prevState) => { const object = { ...prevState.object }; object[key][subkey] = value; return { object }; }); } Console output: newvalueijusttyped newvalueijusttyped This behavior actually goes all the way up to modifying the redux store without ever having dispatched an action. Would appreciate a solution for this issue Update: Changing the constructor to this solved the issue constructor(props) { super(props); this.state = { object: JSON.parse(JSON.stringify(props.object)), hidden: props.hidden, }; } Why doesn't the object spread operator achieve what I'm trying to accomplish?
Javascript object are assigned by reference so when you do constructor(props) { super(props); this.state = { object: props.object, hidden: props.hidden, }; } state is referencing the redux state object(if it is a redux state). So now when you use this.setState((prevState) => { const object = { ...prevState.object }; object[key][subkey] = value; return { object }; }); Although you would assume that you have cloned the object value into a new object. However Spread syntax does only a one level copy of the object. From the Spread Syntax MDN docs: Note: Spread syntax effectively goes one level deep while copying an array. Therefore, it may be unsuitable for copying multidimensional arrays as the following example shows (it's the same with Object.assign() and spread syntax). var a = [1, [2], [3]]; var b = [...a]; b.shift().shift(); // 1 // Now array a is affected as well: [[], [2], [3]] So effectively object[key][subkey] = value; changes the value directly in redux store. Solution is create a nested copy like const object = { ...prevState.object, [key]: { ...prevState[key], [subkey]: { ...prevState[key][subkey]} } }; object[key][subkey] = value;
Objects in javascript are 'passed by reference'. If you are passing the parent's props as state to the children, then when you change the state, you are in effect mutating the very same object that was passed to it. Use Object.assign() to create a clone of the data before you make it part of the child's state.
How can I update state.item[1] in state using setState?
I'm creating an app where the user can design his own form. E.g. specify name of the field and details of which other columns that should be included. The component is available as a JSFiddle. My initial state looks like this: var DynamicForm = React.createClass({ getInitialState: function() { var items = {}; items[1] = { name: 'field 1', populate_at: 'web_start', same_as: 'customer_name', autocomplete_from: 'customer_name', title: '' }; items[2] = { name: 'field 2', populate_at: 'web_end', same_as: 'user_name', autocomplete_from: 'user_name', title: '' }; return { items }; }, render: function() { var _this = this; return ( <div> { Object.keys(this.state.items).map(function (key) { var item = _this.state.items[key]; return ( <div> <PopulateAtCheckboxes this={this} checked={item.populate_at} id={key} populate_at={data.populate_at} /> </div> ); }, this)} <button onClick={this.newFieldEntry}>Create a new field</button> <button onClick={this.saveAndContinue}>Save and Continue</button> </div> ); } I want to update the state when the user changes any of the values, but I'm having a hard time to target the correct object: var PopulateAtCheckboxes = React.createClass({ handleChange: function (e) { item = this.state.items[1]; item.name = 'newName'; items[1] = item; this.setState({items: items}); }, render: function() { var populateAtCheckbox = this.props.populate_at.map(function(value) { return ( <label for={value}> <input type="radio" name={'populate_at'+this.props.id} value={value} onChange={this.handleChange} checked={this.props.checked == value} ref="populate-at"/> {value} </label> ); }, this); return ( <div className="populate-at-checkboxes"> {populateAtCheckbox} </div> ); } }); How should I craft this.setState to get it to update items[1].name ?
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
You could use the update immutability helper for this: this.setState({ items: update(this.state.items, {1: {name: {$set: 'updated field name'}}}) }) Or if you don't care about being able to detect changes to this item in a shouldComponentUpdate() lifecycle method using ===, you could edit the state directly and force the component to re-render - this is effectively the same as #limelights' answer, as it's pulling an object out of state and editing it. this.state.items[1].name = 'updated field name' this.forceUpdate() Post-edit addition: Check out the Simple Component Communication lesson from react-training for an example of how to pass a callback function from a state-holding parent to a child component which needs to trigger a state change.
Wrong way! handleChange = (e) => { const { items } = this.state; items[1].name = e.target.value; // update state this.setState({ items, }); }; As pointed out by a lot of better developers in the comments: mutating the state is wrong! Took me a while to figure this out. Above works but it takes away the power of React. For example componentDidUpdate will not see this as an update because it's modified directly. So the right way would be: handleChange = (e) => { this.setState(prevState => ({ items: { ...prevState.items, [prevState.items[1].name]: e.target.value, }, })); };
To modify deeply nested objects/variables in React's state, typically three methods are used: vanilla JavaScript's Object.assign, immutability-helper and cloneDeep from Lodash. There are also plenty of other less popular third-party libs to achieve this, but in this answer, I'll cover just these three options. Also, some additional vanilla JavaScript methods exist, like array spreading, (see #mpen's answer for example), but they are not very intuitive, easy to use and capable to handle all state manipulation situations. As was pointed innumerable times in top voted comments to the answers, whose authors propose a direct mutation of state: just don't do that. This is a ubiquitous React anti-pattern, which will inevitably lead to unwanted consequences. Learn the right way. Let's compare three widely used methods. Given this state object structure: state = { outer: { inner: 'initial value' } } You can use the following methods to update the inner-most inner field's value without affecting the rest of the state. 1. Vanilla JavaScript's Object.assign const App = () => { const [outer, setOuter] = React.useState({ inner: 'initial value' }) React.useEffect(() => { console.log('Before the shallow copying:', outer.inner) // initial value const newOuter = Object.assign({}, outer, { inner: 'updated value' }) console.log('After the shallow copy is taken, the value in the state is still:', outer.inner) // initial value setOuter(newOuter) }, []) console.log('In render:', outer.inner) return ( <section>Inner property: <i>{outer.inner}</i></section> ) } ReactDOM.render( <App />, document.getElementById('react') ) <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script> <main id="react"></main> Keep in mind, that Object.assign will not perform a deep cloning, since it only copies property values, and that's why what it does is called a shallow copying (see comments). For this to work, we should only manipulate the properties of primitive types (outer.inner), that is strings, numbers, booleans. In this example, we're creating a new constant (const newOuter...), using Object.assign, which creates an empty object ({}), copies outer object ({ inner: 'initial value' }) into it and then copies a different object { inner: 'updated value' } over it. This way, in the end the newly created newOuter constant will hold a value of { inner: 'updated value' } since the inner property got overridden. This newOuter is a brand new object, which is not linked to the object in state, so it can be mutated as needed and the state will stay the same and not changed until the command to update it is ran. The last part is to use setOuter() setter to replace the original outer in the state with a newly created newOuter object (only the value will change, the property name outer will not). Now imagine we have a more deep state like state = { outer: { inner: { innerMost: 'initial value' } } }. We could try to create the newOuter object and populate it with the outer contents from the state, but Object.assign will not be able to copy innerMost's value to this newly created newOuter object since innerMost is nested too deeply. You could still copy inner, like in the example above, but since it's now an object and not a primitive, the reference from newOuter.inner will be copied to the outer.inner instead, which means that we will end up with local newOuter object directly tied to the object in the state. That means that in this case mutations of the locally created newOuter.inner will directly affect the outer.inner object (in state), since they are in fact became the same thing (in computer's memory). Object.assign therefore will only work if you have a relatively simple one level deep state structure with innermost members holding values of the primitive type. If you have deeper objects (2nd level or more), which you should update, don't use Object.assign. You risk mutating state directly. 2. Lodash's cloneDeep const App = () => { const [outer, setOuter] = React.useState({ inner: 'initial value' }) React.useEffect(() => { console.log('Before the deep cloning:', outer.inner) // initial value const newOuter = _.cloneDeep(outer) // cloneDeep() is coming from the Lodash lib newOuter.inner = 'updated value' console.log('After the deeply cloned object is modified, the value in the state is still:', outer.inner) // initial value setOuter(newOuter) }, []) console.log('In render:', outer.inner) return ( <section>Inner property: <i>{outer.inner}</i></section> ) } ReactDOM.render( <App />, document.getElementById('react') ) <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js"></script> <main id="react"></main> Lodash's cloneDeep is way more simple to use. It performs a deep cloning, so it is a robust option, if you have a fairly complex state with multi-level objects or arrays inside. Just cloneDeep() the top-level state property, mutate the cloned part in whatever way you please, and setOuter() it back to the state. 3. immutability-helper const App = () => { const [outer, setOuter] = React.useState({ inner: 'initial value' }) React.useEffect(() => { const update = immutabilityHelper console.log('Before the deep cloning and updating:', outer.inner) // initial value const newOuter = update(outer, { inner: { $set: 'updated value' } }) console.log('After the cloning and updating, the value in the state is still:', outer.inner) // initial value setOuter(newOuter) }, []) console.log('In render:', outer.inner) return ( <section>Inner property: <i>{outer.inner}</i></section> ) } ReactDOM.render( <App />, document.getElementById('react') ) <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script> <script src="https://wzrd.in/standalone/immutability-helper#3.0.0"></script> <main id="react"></main> immutability-helper takes it to a whole new level, and the cool thing about it is that it can not only $set values to state items, but also $push, $splice, $merge (etc.) them. Here is a list of commands available. Side notes Again, keep in mind, that setOuter only modifies the first-level properties of the state object (outer in these examples), not the deeply nested (outer.inner). If it behaved in a different way, this question wouldn't exist. Which one is right for your project? If you don't want or can't use external dependencies, and have a simple state structure, stick to Object.assign. If you manipulate a huge and/or complex state, Lodash's cloneDeep is a wise choice. If you need advanced capabilities, i.e. if your state structure is complex and you need to perform all kinds of operations on it, try immutability-helper, it's a very advanced tool which can be used for state manipulation. ...or, do you really need to do this at all? If you hold a complex data in React's state, maybe this is a good time to think about other ways of handling it. Setting a complex state objects right in React components is not a straightforward operation, and I strongly suggest to think about different approaches. Most likely you better be off keeping your complex data in a Redux store, setting it there using reducers and/or sagas and access it using selectors.
I had the same problem. Here's a simple solution that works ! const newItems = [...this.state.items]; newItems[item] = value; this.setState({ items:newItems });
According to the React documentation on setState, using Object.assign as suggested by other answers here is not ideal. Due to the nature of setState's asynchronous behavior, subsequent calls using this technique may override previous calls causing undesirable outcomes. Instead, the React docs recommend to use the updater form of setState which operates on the previous state. Keep in mind that when updating an array or object you must return a new array or object as React requires us to preserve state immutability. Using ES6 syntax's spread operator to shallow copy an array, creating or updating a property of an object at a given index of the array would look like this: this.setState(prevState => { const newItems = [...prevState.items]; newItems[index].name = newName; return {items: newItems}; })
First get the item you want, change what you want on that object and set it back on the state. The way you're using state by only passing an object in getInitialState would be way easier if you'd use a keyed object. handleChange: function (e) { item = this.state.items[1]; item.name = 'newName'; items[1] = item; this.setState({items: items}); }
Don't mutate the state in place. It can cause unexpected results. I have learned my lesson! Always work with a copy/clone, Object.assign() is a good one: item = Object.assign({}, this.state.items[1], {name: 'newName'}); items[1] = item; this.setState({items: items}); https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
Use array map with arrow function, in one line this.setState({ items: this.state.items.map((item, index) => index === 1 ? { ...item, name: 'newName' } : item, ) })
Sometimes in React, mutating the cloned array can affect the original one, this method will never cause mutation: const myNewArray = Object.assign([...myArray], { [index]: myNewItem }); setState({ myArray: myNewArray }); Or if you just want to update a property of an item: const myNewArray = Object.assign([...myArray], { [index]: { ...myArray[index], prop: myNewValue } }); setState({ myArray: myNewArray });
As none of the above options was ideal to me I ended up using map: this.setState({items: this.state.items.map((item,idx)=> idx!==1 ?item :{...item,name:'new_name'}) })
Mutation free: // given a state state = {items: [{name: 'Fred', value: 1}, {name: 'Wilma', value: 2}]} // This will work without mutation as it clones the modified item in the map: this.state.items .map(item => item.name === 'Fred' ? {...item, ...{value: 3}} : item) this.setState(newItems)
It's really simple. First pull the entire items object from state, updated the part of the items object as desired, and put the entire items object back in state via setState. handleChange: function (e) { items = Object.assign(this.state.items); // Pull the entire items object out. Using object.assign is a good idea for objects. items[1].name = 'newName'; // update the items object as needed this.setState({ items }); // Put back in state }
Found this surprisingly hard and none of the ES6 spread magic seemed to work as expected. Was using a structure like this to get rendered element properties for layout purposes. found using the update method from immutability-helper to be the most straight forward one in this simplified example: constructor(props) { super(props) this.state = { values: [] } this.updateContainerState = this.updateContainerState.bind(this) } updateContainerState(index, value) { this.setState((state) => update(state, { values: { [index]: { $set: value } } })) } as adapted from https://github.com/kolodny/immutability-helper#computed-property-names of the to be updated array member is a more nested complex object use the appropriate deep copy method based on complexity. There are surely better ways to handle layout parameters, but this is about how to handle arrays. The relevant values for each child element could also be computed outside of them, but I found it more convenient to pass containerState down, so they childs can fetch properties at will and Update the parent state array at their given index. import React from 'react' import update from 'immutability-helper' import { ContainerElement } from './container.component.style.js' import ChildComponent from './child-component' export default class ContainerComponent extends React.Component { constructor(props) { super(props) this.state = { values: [] } this.updateContainerState = this.updateContainerState.bind(this) } updateContainerState(index, value) { this.setState((state) => update(state, { values: { [index]: { $set: value } } })) } // ... render() { let index = 0 return ( <ContainerElement> <ChildComponent index={index++} containerState={this.state} updateContainerState={this.updateContainerState} /> <ChildComponent index={index++} containerState={this.state} updateContainerState={this.updateContainerState} /> </ContainerElement> ) } }
#JonnyBuchanan's answer works perfectly, but for only array state variable. In case the state variable is just a single dictionary, follow this: inputChange = input => e => { this.setState({ item: update(this.state.item, {[input]: {$set: e.target.value}}) }) } You can replace [input] by the field name of your dictionary and e.target.value by its value. This code performs the update job on input change event of my form.
Use the event on handleChange to figure out the element that has changed and then update it. For that you might need to change some property to identify it and update it. See fiddle https://jsfiddle.net/69z2wepo/6164/
I would move the function handle change and add an index parameter handleChange: function (index) { var items = this.state.items; items[index].name = 'newName'; this.setState({items: items}); }, to the Dynamic form component and pass it to the PopulateAtCheckboxes component as a prop. As you loop over your items you can include an additional counter (called index in the code below) to be passed along to the handle change as shown below { Object.keys(this.state.items).map(function (key, index) { var item = _this.state.items[key]; var boundHandleChange = _this.handleChange.bind(_this, index); return ( <div> <PopulateAtCheckboxes this={this} checked={item.populate_at} id={key} handleChange={boundHandleChange} populate_at={data.populate_at} /> </div> ); }, this)} Finally you can call your change listener as shown below here <input type="radio" name={'populate_at'+this.props.id} value={value} onChange={this.props.handleChange} checked={this.props.checked == value} ref="populate-at"/>
If you need to change only part of the Array, You've a react component with state set to. state = {items: [{name: 'red-one', value: 100}, {name: 'green-one', value: 999}]} It's best to update the red-one in the Array as follows: const itemIndex = this.state.items.findIndex(i=> i.name === 'red-one'); const newItems = [ this.state.items.slice(0, itemIndex), {name: 'red-one', value: 666}, this.state.items.slice(itemIndex) ] this.setState(newItems)
or if you have a dynamically generated list and you don't know the index but just have the key or id: let ItemsCopy = [] let x = this.state.Items.map((entry) =>{ if(entry.id == 'theIDYoureLookingFor') { entry.PropertyToChange = 'NewProperty' } ItemsCopy.push(entry) }) this.setState({Items:ItemsCopy});
Try with code: this.state.items[1] = 'new value'; var cloneObj = Object.assign({}, this.state.items); this.setState({items: cloneObj });
Following piece of code went easy on my dull brain. Removing the object and replacing with the updated one var udpateditem = this.state.items.find(function(item) { return item.name == "field_1" }); udpateditem.name= "New updated name" this.setState(prevState => ({ items:prevState.dl_name_template.filter(function(item) { return item.name !== "field_1"}).concat(udpateditem) }));
How about creating another component(for object that needs to go into the array) and pass the following as props? component index - index will be used to create/update in array. set function - This function put data into the array based on the component index. <SubObjectForm setData={this.setSubObjectData} objectIndex={index}/> Here {index} can be passed in based on position where this SubObjectForm is used. and setSubObjectData can be something like this. setSubObjectData: function(index, data){ var arrayFromParentObject= <retrieve from props or state>; var objectInArray= arrayFromParentObject.array[index]; arrayFromParentObject.array[index] = Object.assign(objectInArray, data); } In SubObjectForm, this.props.setData can be called on data change as given below. <input type="text" name="name" onChange={(e) => this.props.setData(this.props.objectIndex,{name: e.target.value})}/>
this.setState({ items: this.state.items.map((item,index) => { if (index === 1) { item.name = 'newName'; } return item; }) });
handleChanges = (value, key) => { // clone the current State object let cloneObject = _.extend({}, this.state.currentAttribute); // key as user.name and value= "ABC" then current attributes have current properties as we changes currentAttribute[key] = value; // then set the state "currentAttribute" is key and "cloneObject" is changed object. this.setState({currentAttribute: cloneObject}); and Change from Text box add onChange event onChange = { (event) => { this.handleChanges(event.target.value, "title"); } }
Try this it will definetly work,other case i tried but didn't work import _ from 'lodash'; this.state.var_name = _.assign(this.state.var_name, { obj_prop: 'changed_value', });