react redux not changes component after store change

I'm stating to learn react and redux so i think there are many things that i don't know. I have a problem with missed re-rendering component on store changes.

This is my project structure: https://i.stack.imgur.com/tJJSg.png

And here is my code:

App.js:

class App extends Component {

    render() {

        return (
            <div className="App">
                <Nav sortByDate={()=>{this.props.sortBy(SORT_BY_DATE)}} sortByLikes={()=>{this.props.sortBy(SORT_BY_LIKES)}} />
                <Items comments={this.props.comments} getList={()=>{this.props.sortBy(GET_LIST)}}/>
            </div>
        );
    }
}

const mapStateToProps = (state) => {
    return {
        comments: state.comments
    };
};

const mapDispatchToProps = (dispatch) => {
    return {
        sortBy: (action) => {
            dispatch(sortBy(action));
        }
    };
};

export default connect (mapStateToProps, mapDispatchToProps) (App);

CommentList.js:

class ListItems extends Component {

    constructor(props){
        super(props);
        this.state = {
            comments: props.comments
        };
    }

    componentWillMount() {
        this.props.getList();
    }

    componentWillReceiveProps(nextProps) {
        if (this.props.comments !== nextProps.comments) {
            this.setState({
                comments: nextProps.comments
            });
        }
    }


    getComments() {
        return (this.state.comments.map(function (object) {
            return <Item numLikes={object.num_like} id={object.id} comment={object.comment} date={object.date}
                         sender={object.sender}/>
        }));
    }


    render() {
        return (
            <Container>
                <Row>
                    <Col lg={2} md={1} xs={0}/>
                    <Col lg={8} md={10} xs={12}>
                        {this.getComments()}
                    </Col>
                    <Col lg={2} md={16} xs={0}/>
                </Row>
            </Container>
        );
    }
}

export default ListItems;

Reducers.js:

const listReducer = (state = {comments: []}, action) => {

    function toDate(dateStr) {
        const [day, month, year] = dateStr.split("/")
        return new Date(year, month - 1, day)
    }

    function commentSortedByDate(comments) {

        const sorted = comments.sort(function(a, b) {
            return  toDate(b.date) - toDate(a.date);
        })

        return sorted;
    }

    function commentSortedByLikes(comments) {
        const sorted = comments.sort(function(a, b) {
            return  parseInt(b.num_like) - parseInt(a.num_like);
        })

        return sorted;
    }



    switch (action.type) {
        case SORT_BY_DATE:
            console.log("sort by date");

            state={
                comments: commentSortedByDate(state.comments)
            }
            break;

        case SORT_BY_LIKES:
            console.log("sort by likes");

            state={
                comments: commentSortedByLikes(state.comments)
            }
            break;

        case GET_LIST:
            state = {
                comments: action.payload
            }
            break;
    }
    return state;
};

export default listReducer;

The problem is certainly with this two components. I have 3 actions:

  • GET_LIST (in a middleware call a rest service getting the json of comments and update the store).
  • SORT_BY_DATE (in the reducer sort the array of comments by date and update the store).
  • SORT_BY_LIKES (same).
    The comments in the store are effectively sorted.

First of all the app dispatch automatically the GET_LIST action and it works, pass the props with comments correctly to the CommentList.js component and successfully render the list of CommentItem.

Now the problem: The click of a button in the Navbar component will dispatch a SORT_BY action that updates the store and finally calls the MapStateToProps function in App, but this time the CommentList stay the same and componentWillReceiveProps is not called.

Why? Can anyone help me?

1 answer

  • answered 2018-03-22 16:59 Tomasz Mularczyk

    You are mutating the state (sort function) instead of creating a new Array in your reducer. This prevents the component from re-rendering as it is not notified of a change. To fix it you could make your functions pure:

    function commentSortedByDate(comments) {
        const copy = [...comments];
        copy.sort(function(a, b) {
            return  toDate(b.date) - toDate(a.date);
        })
        return copy;
    }
    
    function commentSortedByLikes(comments) {
        const copy = [...comments];
        copy.sort(function(a, b) {
            return  parseInt(b.num_like) - parseInt(a.num_like);
        })
        return copy;
    }
    

    This way you return a new array instead of old one (with sorted elements).