/  Technology   /  Updating properties of an object in React state
Updating properties of an object in React state

Updating properties of an object in React state

 

Despite that, as known by bulk of you, react setState is asynchronous, and React is smart enough to handle different setState in one action:

clickHandler() {
  this.setState({
    a: 1
  });
  this.setState({
    b: 2
  });
}render() {
  console.log('render');
  return <button onClick={this.clickHandler}/>;
}

You will notice that, not only both a and b get updated ,when clicking the button, in the console, there is only one “render” printed out.

What if you have an object in the react state, and want to update various properties of the object, does it still work the same way? . Let’s see how we are going to update an object in react setState . Imagine we have an object and we use a react form to edit it.

this.state = {
  todoList: {
    day: '' // Monday, Tuesday, etc...
    items: []
  }
}

In the form, there is a dropdown list to choose the day from Monday to Sunday. Depending on which day you want to select, there will be another list of items for multi-select. The use case here is, if you choose Monday, and select a bunch of items, then you want to choose another day, say Tuesday. What happens to the list of items(options) that you just selected for Monday? We can not keep that list as it becomes invalid since the day is no long Monday.

Of course we want to do some sort of clean-up. The most straight-forward way is to empty the items list(options). Let’s take a look at the code (we will use spread operator and some ES6 here, come on, you gotta know it).

onDaySelect(day) {
this.setState({
todoList: {
...this.state.todoList,
day,
items: []
}
})
}

So, we are changing day and items(options) in one react setState . There is absolutely no problem here.

What if, the todoList is way most complex than this example. E.g. There are more checkboxes and inputs in this form. I would like to have a generic method to update the react object.

setObjectByPath(fieldPath, value) {
this.setState({
todoList: R.set(R.lensPath(fieldPath), value, this.state.todoList)
})
}

In this way, no matter how complicated the react object is, you can easily set a value to a property, even it is nested in objects or arrays. (Using lensPath here so that you can set existences like todoList.someObject.someNestedField .

Here is how we can use it in the above example:

onDaySelect(day) {
  this.setObjectByPath(['day'], day);
}onItemsSelect(items) {
  this.setObjectByPath(['items'], items);
}onSomeNestedFieldChange(value) {
  this.setObjectByPath(['objectName', 'fieldName'], value);
}

So, let’s apply to the above example in onDaySelect .

onDaySelect(day) {
this.setObjectByPath(['day'], day); // 1
this.setObjectByPath(['items'], []); // 2
}

You will soon notice that, it some how does not work properly.

At first time , I thought, something was wrong with React, because it did not update both properties for me. In fact, the first line does not works. Why?

I will skip the boring explanation. In the setObjectByPath function(), and it always takes react this.state.todoList as the origin. As react setState is async, when two setObjectByPath are called, the state has not been changed at all. Imagine the todoList objecst is like this before the update.

{day: 'Monday', items: [1,2,3]}

Here is what actually happens when we call the react onDaySelect function():

{day: 'Monday', items: [1,2,3]} -> {day: 'Tuesday', items: [1,2,3]}
{day: 'Monday', items: [1,2,3]} -> {day: 'Monday', items: []}

The problem is not on React, it is on the function I wrote! Perhaps…

It means that, you can not use that beautiful generic setObjectByPath() method if you need to change more than one property at a time in react setState. How lame it is? ! Same thing happens to react Redux .

Unlike react Redux , React is smart that you do not need to always put the final outcome of the new react state in the setState function(). You expect property b remains while you can only put a in react setState . While in react Redux , if you want only return a in the new state, do not expect you can find b anymore. That’s means, you need to take care of the entire state object in react Redux . That also means, you need to put all the specific logic in one place and can not delegate to multiple handlers.

It ends up with something like this in the setObjectPath():

setObjectPath(fieldPath, value) {
if (fieldPath[0] === 'day') {
this.setState({
R.compose(R.set(), R.set()) // sets day and items together
})
} else if // more other cases...
}

 

Leave a comment