341

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 here.

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 ?

Neurotransmitter
  • 4,619
  • 2
  • 43
  • 33
martins
  • 7,647
  • 6
  • 41
  • 67

25 Answers25

263

Since there's a lot of misinformation in this thread, 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
mpen
  • 237,624
  • 230
  • 766
  • 1,119
  • 5
    @TranslucentCloud Oh yeah, the helper methods are definitely nice, but I think everyone should know what's going on under the hood :-) – mpen Apr 05 '18 at 20:01
  • Why do we need step 2 and 3? Why can't we mutate the clone after the first step directly? – Evmorov Dec 05 '18 at 06:09
  • 4
    @Evmorov Because it's not a deep clone. Step 1 is just cloning the array, not the objects inside. In other words, each of the objects inside the new array still "point to" the existing objects in memory -- mutating one will mutate the other (they're one and the same). See also the `items[0] === clone[0]` bit in my terminal example at the bottom. Triple `=` checks if the objects refer to the same thing. – mpen Dec 05 '18 at 22:12
  • 1
    Becomes more complicated if you don't know the item's index in the array. – Ian Warburton Jun 10 '19 at 14:58
  • 3
    @IanWarburton Not really. [`items.findIndex()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex) should make short work of that. – mpen Jun 10 '19 at 21:58
  • How do you make step to variable? Meaning, instead of hard coding the index, how do you make it so that it adapts to the field you're updating? I hope that makes sense. – athomas Mar 26 '20 at 12:37
  • @athomas Just replace `1` with `i` and `2` with `i+1`. To find the index, you can use [findIndex](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex). – mpen Mar 26 '20 at 17:38
  • This worked beautifully for me. I did need to add the key to the state object: `this.setState({ filteredRows: rows })` – isherwood Jun 10 '20 at 13:29
  • I think this slice method is even faster then the update thing from the immutability helper. – Zoltán Hajdú Sep 11 '20 at 20:45
  • 1
    Thank so much. I was struggling to update an element of state an array. You saved my time a lot. – Watanabe.N Apr 19 '21 at 13:40
133

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.

Jonny Buchanan
  • 58,371
  • 16
  • 137
  • 146
  • Reading this answer: https://stackoverflow.com/a/51018315/2396744, I think you still need the updater form of the setstate function, that uses prevstate callback. – isgoed Oct 16 '20 at 12:52
  • Thanks a lot, Jonny, wasted one day and finally get solution. – Himanshu padia Mar 05 '21 at 06:34
102

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,
        },
    }));
};
user3773048
  • 4,123
  • 3
  • 14
  • 21
MarvinVK
  • 2,489
  • 1
  • 15
  • 19
  • 26
    ES6 just because you're using "const"? – nkkollaw Jul 21 '16 at 15:34
  • Interesting that this works, can you explain a bit? Thank you! – vaskort Dec 04 '16 at 00:17
  • @BillK You just assign your current state to a const (var) then you change the role in that const to the value of the target and then you just update the state (using a shorthand instead of items: items) withe the new data. – MarvinVK Dec 16 '16 at 13:32
  • Ah ok got confused with this shorthand, thank you mate @MarvinVK – vaskort Dec 16 '16 at 17:37
  • 51
    Isn't calling `items[1].role = e.target.value` mutating state directly? – antony Mar 20 '17 at 17:33
  • 30
    you are mutating the state, this is totally against the idea of maintaining the state immutable like react suggested. This can give you a lot of pain in a big application. – ncubica Apr 21 '17 at 01:26
  • 4
    To be specific, the downside of mutating state is you can't perform a === comparison in shouldComponentUpdate, and your state changes may be overridden when setState runs. https://facebook.github.io/react/docs/react-component.html#state – Cory House Jun 10 '17 at 21:26
  • 1
    @TranslucentCloud It's just my writing style and my eslint loves it :) – MarvinVK Jul 10 '17 at 11:10
  • 1
    This recommends to go behind React’s back and mutate something which should be treated as immutable. – binki Jul 31 '17 at 01:26
  • 8
    @MarvinVK, your answer says "So best practice would be:" followed by use of "this.forceUpdate();" which is not recommended as it could be overwritten by setState(), see https://facebook.github.io/react/docs/react-component.html#state. Best to change this so it's not confusing to future readers. – James Z. Aug 01 '17 at 07:27
  • 2
    @JamesZ. not only this, but a lot of other answers here propose a 'solution' to directly mutate the state, even the top-voted ones. I should probably downvote and comment on all of them, and you should too, for the better! And upvote the correct methods. Newbs will be grateful. – Neurotransmitter Oct 22 '17 at 11:01
  • 2
    @MarvinVK, You are still mutating the state arn't you? Basically: `this.state.items[1].role = e.target.value;` . You must know this is wrong. It's very confusing that it has so many upvotes. **You should delete this answer.** – Buh Buh Feb 06 '18 at 11:17
  • 1
    This answer is misleading due to the high number of up-votes. You're mutating state directly. This is exactly how not to update state in react – PrashanD Feb 22 '18 at 05:01
  • To avoid mutating state, you can use ES6 spread operator to make a copy of the list. The functional setState also avoids mutating state. Both of these are preferable to this answer. Here is an example of both. ``` this.setState(prevState => { const items = [...prevState.items] items[1].role = e.target.value return {items} }) – Corey Apr 25 '18 at 15:31
  • 15
    Just wanted to point out that a lot of the comments are irrelevant now due to edits and *the right way* is actually the right way. – heez Jan 13 '19 at 15:42
  • This should be downvoted more. `item` is not defined in your "right way" – mmla Feb 12 '19 at 18:48
  • This solution will not work i believe, since you use the value of name as key – jpmarks Jul 03 '20 at 08:08
  • The correct way is not working for me. Look at @Jonas answer, that one worked for me – tif Jul 22 '20 at 07:41
56

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.

Community
  • 1
  • 1
Neurotransmitter
  • 4,619
  • 2
  • 43
  • 33
  • `Object.assign` does not perform a deep copy. Say, if `a = {c: {d: 1}}` and `b = Object.assign({}, a)`, then you execute `b.c.d = 4`, then `a.c.d` is mutated. – Awol Nov 07 '17 at 20:06
  • You're right, the value `1` of the innermost object (`a.c.d`) will get mutated. But if you will reassign the first-level successor of `b`, like this: `b.c = {f: 1}`, the corresponding part of `a` will not get mutated (it'll stay `{d: 1}`). Nice catch anyway, I'll update the answer right away. – Neurotransmitter Nov 07 '17 at 20:23
  • What you have defined is actually a `shallow copy` and not a `deep copy`. It's easy to confuse what `shallow copy` means. In `shallow copy`, `a !== b`, but for each key from source object `a`, `a[key] === b[key]` – Awol Nov 08 '17 at 12:26
  • Yeah, explicitly mentioned shallowness of `Object.assign` in the answer. – Neurotransmitter Feb 20 '18 at 11:44
  • `JSON.parse(JSON.stringify(object))` is also a variant for deep clone. The performance is worse then lodash `cloneDeep` though. https://www.measurethat.net/Benchmarks/Show/2751/0/lodash-clonedeep-vs-json-clone – tylik Apr 24 '19 at 10:44
  • @tylik yeah, for a long time. They changed something and the test is gone. And I still didn't take my time to re-write those tests. Seems like I should delete the link for now. – Neurotransmitter Apr 25 '19 at 10:49
  • I would personally avoid `JSON.parse(JSON.stringify(object))` like a plague. Although it works most of the time, It is not meant for this task. – Neurotransmitter Feb 11 '21 at 12:37
42

I had the same problem. Here's a simple solution that works !

const newItems = [...this.state.items];
newItems[item] = value;
this.setState({ items:newItems });
vsync
  • 87,559
  • 45
  • 247
  • 317
Jonas
  • 541
  • 5
  • 3
  • 11
    @TranslucentCloud - this is most certainly not a direct mutation. the original array was cloned, modified and then the state was set again using the cloned Array. – vsync Jul 16 '18 at 14:51
  • @vsync yeah, now after you edited the original answer this is not a mutation at all. – Neurotransmitter Jul 18 '18 at 13:58
  • 2
    @TranslucentCloud - Before also, my edit has nothing to do with it, thanks a lot for the attitude. @Jonas here only made a simple mistake in his answer, using `{` curly braces instead of brackets which I had remedied – vsync Jul 18 '18 at 16:19
36

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};
})
eicksl
  • 724
  • 6
  • 8
28

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});
}
Henrik Andersson
  • 37,809
  • 15
  • 88
  • 86
  • 3
    Nah, it yields `Uncaught TypeError: Cannot read property 'items' of null`. – martins Apr 09 '15 at 11:40
  • No it won't. Your error most likely stems from the way you're doing `getInitialState`. – Henrik Andersson Apr 09 '15 at 11:40
  • Added more code to the post to get a better context. Please have a look. – martins Apr 09 '15 at 11:42
  • How do you suggest I should design `getInitialState` ? Can you please give me an example? – martins Apr 09 '15 at 11:45
  • Aha, that state in `PopulateCheckboxes` doesn't have access to your `DynamicForm`s state. You need to pass `items` as props if you want to "access" that. – Henrik Andersson Apr 09 '15 at 11:47
  • 10
    @HenrikAndersson Something doesn't seem right in your example. `items` is not defined anywhere. – Edward D'Souza Jun 15 '16 at 17:14
  • 1
    @EdwardD'Souza You're absolutely correct! My answer is to show how it should be defined and used. The way the askers code is setup does not work for the thing he/she would like to which is why the need for a keyed object is required. – Henrik Andersson Jun 15 '16 at 21:43
  • 7
    This is a typical React anti-pattern, you're assigning `item` a reference to the state's own `this.state.items[1]` variable. Then you modify `item` (`item.name = 'newName'`), and thus mutate state directly, which is highly discouraged. In your example, there is even no need to call `this.setState({items: items})`, because state is already mutated directly. – Neurotransmitter Oct 22 '17 at 10:54
  • you are mutating the original object. That is a big no no – Andy Apr 03 '21 at 01:40
22

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

kabirbaidhya
  • 2,581
  • 2
  • 29
  • 51
Jean Luo
  • 329
  • 2
  • 3
  • 8
    what is `items` in your example? Did you mean `this.state.items` or something else? – Buh Buh Feb 06 '18 at 11:27
  • I tested this but he is missing one line. Above `items[1] = item;` there should be a line saying `items = this.state.items;`. Beware my javascript is rusty and I'm learning react for my home project so I have no idea if this is good or bad :-) – Greg0ry Oct 06 '19 at 16:20
6

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

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)
John Wundes
  • 184
  • 1
  • 4
3

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
}
John
  • 760
  • 10
  • 18
  • "Object.assign will work if you have a relatively simple one level deep state structure with innermost members holding values of primitive type". – Neurotransmitter Nov 05 '18 at 11:14
3

Use array map with arrow function, in one line

this.setState({
    items: this.state.items.map((item, index) =>
      index === 1 ? { ...item, name: 'newName' } : item,
   )
})
Rasta_boy
  • 309
  • 3
  • 5
2

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 });
james
  • 107
  • 1
  • 8
1

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>
    )
  }
}
mxfh
  • 306
  • 2
  • 10
0

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/

stringparser
  • 745
  • 1
  • 6
  • 16
0

How about creating another component(for object that needs to go into the array) and pass the following as props?

  1. component index - index will be used to create/update in array.
  2. 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})}/>
Mo.
  • 21,971
  • 31
  • 138
  • 201
0

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"/>
frandroid
  • 1,105
  • 15
  • 24
0

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)
AA Shakil
  • 478
  • 4
  • 14
att
  • 199
  • 3
  • 9
  • what is ```newArray```? do you mean ```newItems```? If you do, wouldn't that leave the state with only one item afterward? – micnil Oct 18 '17 at 17:45
  • This will introduce a new property `newItems` to the `state` object, and will not update the existing `items` property. – Neurotransmitter Jan 17 '18 at 10:09
0

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});
Jared
  • 678
  • 8
  • 12
0

Try with code:

this.state.items[1] = 'new value';
var cloneObj = Object.assign({}, this.state.items);

this.setState({items: cloneObj });
searching9x
  • 1,293
  • 14
  • 13
0

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)
    }));
Shan
  • 71
  • 6
0
this.setState({
      items: this.state.items.map((item,index) => {
        if (index === 1) {
          item.name = 'newName';
        }
        return item;
      })
    });
  • 1
    this is so not optimal, you iterate the whole array in order to update only the second item? – wscourge Apr 08 '20 at 11:45
  • You're also mutating the element in the first array too, you should use `item = Object.assign({}, item, {name: 'newName'});` – remram Jun 17 '20 at 16:16
0

@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.

Serge Kishiko
  • 316
  • 3
  • 11
-3
 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");
   }
}
-4

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',
});
nirav jobanputra
  • 315
  • 1
  • 2
  • 12