Search code examples
javascriptreactjsreduxreact-reduxweb-component

Components Not Re-Rendering when Action is Dispatched and State Updates


When I click on one of my InspectorOption components, my redux logger shows that an action is dispatched and the state updates as expected.

My InspectorSelect and children InspectorOption components use react-redux's connect to mapStateToProps and these component depend on those props that come from state.

But even though the state is updating and the components depend on state, the components are not re-rendering when the state updates.

Why are my components not re-rendering when the state changes and how do I correct this?

@connect((state) => {
    return {
        options: state.inspector.options
    }
})
export default class InspectorSelect extends Component {
    render() {
        return (
            <div>
                {
                    this.props.options.map(option => {
                        return <InspectorOption 
                            option={ option }
                            key={ option.id }
                        />
                    })
                }
            </div>
        )
    }   
}

https://github.com/caseysiebel/dashboard/blob/master/src/components/InspectorSelect.js#L17


Solution

  • As @markerikson noted: 99.9% of the time, it's due to accidental mutation of Redux state in a reducer

    There is a mutation in dashboard/src/reducers/inspector.js

    export default function reducer(state = {
        options: [] 
    }, action) {
    
        switch (action.type){
            case 'SET_INSPECTOR': 
                state.options = state.options.map(option => {   // <-- mutation here
                    return option.id === action.payload ?
                        { ...option, active: true } :
                        { ...option, active: false }
                })
                return state // returning mutated state
            default: 
                return state
        }
    }
    

    should be

    export default function reducer(state = {
        options: [] 
    }, action) {
    
        switch (action.type){
            case 'SET_INSPECTOR': 
                var newOptions = state.options.map(option => {
                    return option.id === action.payload ?
                        { ...option, active: true } :
                        { ...option, active: false }
                });
                return {...state, options: newOptions}  // returning new state
            default: 
                return state
        }
    }