React not updating property from an object inside an array

- 1 answer

Ad

What I'm trying to do is dynamically add in new elements and change their styles sometime in the future. So to do that, I tried creating an object that would go into the state and would contain two attributes that have an array value set to them: one being an array that holds the element itself and the other that holds the style associated with that.

But for some reason, even though I tried creating a copy of the object holding the two attributes (someone said to treat the state as if it's immutable on stackoverflow) and updating that, then setting the state to the new object, it still doesn't seem to update.

I also tried the solution from here, but it still gives the same problem.

var React = require('react');
var ReactDOM = require('react-dom');
var update = require('react-addons-update');


var Book = React.createClass({
    getInitialState() {
        return {
            pages: {
                pageElements: [], pageStyle: []
            }
        };
    },
    changeStyle: function (e) {
        this.setState(update(this.state.pages.pageStyle[0],
            {name: {$set: {position: 'absolute', transform: 'translate(50px,100px)'} }}));
    },
    componentDidMount: function () {
        let pagesCopy = {
            pageElements: this.state.pages.pageElements, pageStyle: []
        };
        pagesCopy.pageStyle.push({styles: {position: 'absolute'}});
        pagesCopy.pageElements.push(
            <img
                key={0}
                onMouseDown={this.changeStyle}
                style={this.state.pages.pageStyle[0]}
                src={'/pages/0'}
                height="800px"
                width="600px"/>);
        this.setState({pages: pagesCopy});
    },
    render: function () {
        return (
            <div>
                <section style={{position: 'relative', height:800, width:1200, margin: 'auto'}}>
                    {this.state.pages.pageElements}
                </section>
            </div>
        )
    }
});

ReactDOM.render(<Book/>, document.getElementById("content"));

I think the problem lies within the following line:

style={this.state.pages.pageStyle[0]}

where it's referencing the object directly. I think it's taking that value and setting that value to the style permanently and since there can only be copies of states, it can't see the updated value on the new object holding the two attributes. I also did research on how to get a reference to the array index so that it fetches it everytime when I update the state, but in the end, it ultimately just makes that value final and keeps it static (I think).

Note: the code is a super simplified version of a larger code, but the code I have here is the basic that's causing the problem

Ad

Answer

Ad

First, I wanted to point out something in your code that I think is a typo. In componentDidMount(), you set the following prop on the img component that you push into pagesCopy:

style={this.state.pages.pageStyle[0]}

Since this.state.pages.pageStyle is an empty array at that time, I believe you meant to set the prop as follows:

style={pagesCopy.pageStyle[0]}

Once that's fixed, I think you've correctly diagnosed the proximate cause. That is, the img component you've stored in this.state.pages.pageElements[0] contains a reference to an object (pagesCopy.pageStyle[0], which later becomes this.state.pages.pageStyle[0]). When you call changeStyles(), you are creating a new object rather than mutating the old one. This is a problem because the styles prop on the img component still contains a reference to the old object, which is unchanged.

If you need to make this work in a pinch, my suggestion would be to directly mutate the object in this.state.pages.pageStyle[0] in the changeStyles() method and then call forceUpdate(). Calling forceUpdate() will cause a re-render. The img component will re-render and its style prop will contain a reference to the old object with the new property values. The following code would do the trick:

changeStyle: function() {
  var styles = this.state.pages.pageStyle[0];
  styles.position = 'absolute';
  styles.transform = 'translate(50px,100px)';
  this.forceUpdate();
}

Now, I will emphasize that this is very, very bad practice. Mutating state directly is almost always a bad idea. If there's a good reason to do it, I haven't heard it yet.

Below is a suggestion for how you might do this without mutating state directly.

var Book = React.createClass({

  getInitialState() {
    return {
      pageElements: [
        {
          tagName: 'img',
          props: {
            src: 'http://www.keenthemes.com/preview/metronic/theme/assets/global/plugins/jcrop/demos/demo_files/image1.jpg',
            height: '800px',
            width: '600px',
            style: {
              position: 'absolute'
            }
          }
        }
      ]
    };
  },

  changeStyle: function(e) {
    this.setState(function(previousState) {
      var newState = _.extend({}, previousState);
      newState.pageElements[0].props.style = {
        position: 'absolute',
        transform: 'translate(50px,100px)'
      };
      return newState;
    });
  },

  render: function () {
    return (
      <div>
        <section style={{ position: 'relative', height: 800, width: 1200, margin: 'auto' }}>
          {
            this.state.pageElements.map(function(pageElement, index) {
              return React.createElement(pageElement.tagName,
                _.extend(pageElement.props, {
                  key: index,
                  onMouseDown: this.changeStyle.bind(this)
                })
              );
            }.bind(this))
          }
        </section>
      </div>
    );
  }

});

React.render(<Book/>, document.getElementById('content'));
Ad
source: stackoverflow.com
Ad