17

This question might fall a little on the side of a "best practice" question, but please bear with me.

Here is a portion of my state:

this.state = {
  typeElements: {
    headers: [
        {
          name: "h1",
          size: 70,
          lineHeight: 1.25,
          kearning: 0,
          marginAfter: 0
        }, {
          name: "h2",
          size: 70,
          lineHeight: 1.25,
          kearning: 0,
          marginAfter: 0
        }, {
          name: "h3",
          size: 70,
          lineHeight: 1.25,
          kearning: 0,
          marginAfter: 0
        }...

What I need to do is REPLACE the object at a given index on the headers array.

I don't know how to do that with the setState method as in this.setState(headers[1] = {obj}) - but that's obviously invalid. My current method is creating a new array and clobbering the old one like this:

_updateStyle(props) {
  let newState = Object.assign({}, this.state)
  newState.typeElements.headers[props.index] = props
  this.setState(newState)
};

For my small hacky project I guess it's OK but I feel like this is super heavy handed and would quickly lead to performance issues at any kind of scale.

4 Answers 4

20

Updated: since this answer still gets upvotes, be aware that the previous answer below is outdated with modern JavaScript and React. The "update" addon is now legacy and "immutability-helper" can be used instead.

The React docs also mention why immutability is important so avoid mutating state. For immutable updates you can use Object.assign() or spread syntax which needs to be done for every level of nesting, like in this example the nested headers object and its array elements. In this particular example we can use the array index as key so it's possible to also use the spread operator to make a shallow clone of the array and assign a new object as value at given index in the cloned array.

_updateStyle (props) {
  const { typeElements } = this.state;
  const updatedHeaders = [...typeElements.headers];
  updatedHeaders[props.index] = props;
  this.setState({
    ...this.state,
    typeElements: {
      ...typeElements,
      headers: updatedHeaders
    }
  ));
}

Another solution which doesn't require the spread syntax and is needed if we are not using the array index to find the object we want to replace, is using array.map to create a new array and returning the new object instead of the old one at given index.

  const updatedHeaders = typeElements.headers.map((obj, index) => {
    return index === props.index ? props : obj;
  });

Similar examples in the Redux docs also explain "immutable update patterns".

React has some immutability helpers for this, which is explained in the docs: https://facebook.github.io/react/docs/update.html

In your case you could use the $splice command to remove one item and add the new one at given index, for example:

_updateStyle (props) {
  this.setState(update(this.state.typeElements, 
    { $splice: [[props.index, 1, props]] }
  ));
}
Sign up to request clarification or add additional context in comments.

2 Comments

aha, I was aware of those but wasn't sure ow to implement, so it goes in the setState method? What does the "1" do? Is that the insertion point of the new object? Could I not just use props.index again for that if that is the case?
Check out the array.splice() method developer.mozilla.org/nl/docs/Web/JavaScript/Reference/… and you will understand... the array holds the arguments passed to the splice method, so the "1" in this example is the deleteCount.
5

Offering a better explanation of how to accomplish this.

  • First, find the index of the element you're replacing in the state array.
  • Second, update the element at that index
  • Third, call setState with the new collection
import update from 'immutability-helper';

// this.state = { employees: [{id: 1, name: 'Obama'}, {id: 2, name: 'Trump'}] } 

updateEmployee(employee) {
    const index = this.state.employees.findIndex((emp) => emp.id === employee.id);
    const updatedEmployees = update(this.state.employees, {$splice: [[index, 1, employee]]});  // array.splice(start, deleteCount, item1)
    this.setState({employees: updatedEmployees});
}

Comments

1

use immutability-helper

you can find nice examples there

Comments

0

Object.Assign uses shallow copy, not deep copy.

Please be aware that in your example Object.assign({}, this.state) copies only links to the nearest children of the state, i.e. to typeElements, but headers array is not copied.

There is a syntactic sugar ... in ES6 for Object.Assign, Babel has the special addon transform-object-rest-spread.

let newHeaders = { ...this.state.typeElements.headers};
newHeaders[index] = props.Something;

let newState = {...state, typeElements: { newHeaders }};

2 Comments

Really? It actually did copy the headers for me too. Can you elaborate?
@motleydev The Object.assign() method only copies enumerable and own properties from a source object to a target object (MDN developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…). It means that it copies only references for the nearest objects or values for primitives (numbers, strings). You get the old array in your implementation and if you mutate it then it also will affect other places where it is in use.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.