David McIntyre
David McIntyre

Reputation: 74

How Redux's mapStateToProps triggers a React component update

In my react/redux project, I have an action that sorts an array:

case 'SORT_BY_NAME':  
  var newList = state.list; //.slice() 
  newList.sort(function(a,b){
    if (a.name > b.name){
      return 1;
    } else if (a.name < b.name){
      return -1;
    } else {
      return 0;
    }
  });    
  return Object.assign({}, state, { list: newList } );
The action successfully sorted the array according to the desired field, the state object was shaped as i expected, and in my react component mapStateToProps was being hit, and within mapStateToProps the state value accurately reflected the sorted state of the array.

The initial rendering was fine. But the sort action failed to result in my component's render method being invoked again, so the data in the UI never appeared sorted.

The architecture of my project is similar to the Reddit example in the Redux docs. However, I also use react-redux's connect with mapStateToProps. Below is a stripped down version of my component design:

class MyComponent extends React.Component{
	render(){
		<ul> 
		    {list.map(item =>
		      <li key={item.id}> 
			        {item.name}  
		      </li>
		    )}
	  	</ul>  
	}
} 

function mapStateToProps(state){  
  return {
	list: state.list;
	}
}
export default connect(
  mapStateToProps,
  mapDispatchToProps
)(MyComponent)
Eventually I realized that the simple solution to my problem was to clone the list before I sorted it using slice(). Therefore my question is not how to fix the problem, but why this problem occurred. Is the issue that redux's mapStateToProps was not invoking render()? Or that the react component did not see a new state object?

By setting a breakpoint within mapStateToProps, it looks as if the state is being set/changed properly; according to the Redux devtools in the Chrome debugger, the store looks as I think it should.

My project is too large to create a demo fiddle that exactly reproduces my problem. When I attempted to create a minimally-sized fiddle to reproduce the issue, I was further perplexed because the issue does NOT occur when I am not using Redux's connect. Link to fiddle.

To summarize, my question is why the sort was not taking effect when using mapStateToProps. My best guess is that if my sort action mutates the previous state so that the new state matches the previous state, then there is no variance that requires a re-rendering. Perhaps mapStateToProps compares previous and new states, and it doesn't compare new props to props' representation in the DOM.

Upvotes: 1

Views: 3075

Answers (1)

Dan Abramov
Dan Abramov

Reputation: 268255

React Redux connect() is heavily optimized and operates under the assumption that mapStateToProps is a pure function.

By calling state.list.sort() you are mutating the store state with is not allowed in Redux. Generally it’s a good idea to remember which methods are mutating, and which are not. For example, splice() is mutation but slice() isn’t; push() is mutating but concat() isn’t. You should never use mutating methods with the state obtained from the store.

You may also look at something like redux-immutable-state-invariant to make these cases easier to detect, or use Immutable collections for JavaScript or seamless-immutable to enforce immutability. This is optional and up to you.

Upvotes: 6

Related Questions