Timo Paul
Timo Paul

Reputation: 945

React, Redux, mapStateToProps and no rerendering despite state changes

I have a strange problem with Redux and React. The state changes properly and in mapStateToProps I also get the right new state but after the mapping the component will not rerender.

This way, the component doesn't rerender:

import React, { Component } from 'react';
import { connect } from 'react-redux';

class ListItem extends Component {
    render() {
       return (<li>{this.props.item.text}</li>);
    }
}

const mapStateToProps = (state, ownProps) => {
    return {
        item: state.test.byID[ownProps.itemID]
    };
}

export default connect(mapStateToProps)(ListItem);

But if I split the item the components rerender:

import React, { Component } from 'react';
import { connect } from 'react-redux';

class ListItem extends Component {
    render() {
       return(<li>{this.props.text}</li>);
    }
}

const mapStateToProps = (state, ownProps) => {
    return {
        id: state.test.byID[ownProps.itemID].id,
        text: state.test.byID[ownProps.itemID].text
    };
}

export default connect(mapStateToProps)(ListItem);

I have no clue why.

Reducer:

const startState = {
    byID: {
        'a1': {id: 'a1', text: 'test1'},
        'a2': {id: 'a2', text: 'test2'},
        'a3': {id: 'a3', text: 'test3'},
        'a4': {id: 'a4', text: 'test4'},
        'a5': {id: 'a5', text: 'test5'},
        'a6': {id: 'a6', text: 'test6'},
        'a7': {id: 'a7', text: 'test7'},
    },
    all: ['a1', 'a2','a3', 'a4', 'a5', 'a6', 'a7']
};


export default function reducer(state = startState, action) {
    switch (action.type) {
        case 'ADD_TEXT':
            const newState = {...state};
            newState.byID[action.id].text = action.text;
            return newState
        default:
            return state;
    }
}

Any ideas?

Upvotes: 1

Views: 3399

Answers (1)

Xarvalus
Xarvalus

Reputation: 3021

Assuming you are changing the text field in one of byID array's objects. With your code:

newState.byID[action.id].text = action.text;

You are mutating your state, therefore React might have problems with recognising changes since the reference of object in item prop does not change, only it's properties. If there is made shallow check, it simply won't re-render as it "suppose" object does not change at all.

In the second scenario you are returning prop text which is string type, so there won't be shallow check like with object, and equal comparison === will distinguish change and properly re-render component.

For more clarification you might want to look at Immutability in React.

The idea of maintaining immutability with spread operator:

const newState = {...state};

Is okay for top-level reference, but not for nested children objects:

Object spread does a shallow copy of the object. Only the object itself is cloned, while nested instances are not cloned. An easy guide to object rest/spread properties in JavaScript

Although mutating object seems to be the most error prone part in your provided code, the Component React class can deeply investigate object changes (PureComponent does not) and the problem might be related to another issue.

Upvotes: 4

Related Questions