Search code examples
javascriptreactjsflux

shouldComponentUpdate + deepEqual and arrays


Problem: shouldComponentUpdate retrieves previous state with this.state, that doesn't work if you keep reference to array at UserList, and update array entity at UserStore.

PureRenderMixin.js

const deepEqual = require('deep-equal');

module.exports = function pureRenderMixin(Component) {
    Component.prototype.shouldComponentUpdate = function(nextProps, nextState) {
        return !deepEqual(this.props, nextProps) || !deepEqual(this.state, nextState);
    };
    return Component;
};

UserList.react.js

class UserList extends React.Component {

    constructor(props) { 
        super(props);
        this._onChange = this._onChange.bind(this);
    }

    componentWillMount() {
        UsersStore.addChangeListener(this._onChange);
    }

    _onChange() {            
        this.setState({userList: UsersStore.getState()});
    }
}

module.exports = PureRenderMixin(UserList);

UsersStore.js

......
getState() { return _userList; }

switch(action.type) {
   case ActionTypes.UPDATE_USER_FLAG:
       //!!!!!!!!!!!!!!
       //PROBLEM: since UserList.react keep userList reference, there is no way to retrieve previous state inside shouldComponentUpdate
       _userList[action.index].flag = action.flag;
       UsersStore.emitChange();
       break;
}

@taggon solution

thanks to taggon, now I know how to make shouldComponentUpdate keep the reference to previous state:

UsersStore.js

......
getState() { return _userList; }

switch(action.type) {
   case ActionTypes.UPDATE_USER_FLAG:
       //SOLUTION: copy an array, so there will be two versions of _userList[action.index]
       _userList =  _.map(_userList, _.clone);

       _userList[action.index].flag = action.flag;
       UsersStore.emitChange();
       break;
}

Solution

  • I think the problem is in the store. The store would be better to create another array whenever its state is changed.

    For example, think the store as an array:

    var store = [ ];
    
    export default store;
    

    You may want to write add() function like this:

    export function add(newItem) {
      store = [ ...store, newItem ];
      // or write this in es5
      store = store.concat([newItem]);
    
      // trigger change event or dispatch an action here
    }
    

    Likewise, remove() function can be:

    export remove(index) {
      store = [ ...store.slice(0, index), ...store.slice(index+1) ];
    
      // trigger change event or dispatch an action here
    }
    

    In this way, the store dereference the component's state whenever the store is changed. This makesshouldComponentUpdate() return true.

    I hope this helps you.