3
votes

My Application and Store

Using Redux with ReactJS, I am keeping an array of objects (called results) in the store and dispatching actions that sort and manipulate it. In mapStateToProps, I return this results array, which renders the results in a list on the view.

// in Component file
export default class ResultList extends Component {
    constructor(props) {
        super(props);
        this.renderResults = this.renderResults.bind(this);
    }

    renderResults(results) {
        return (
            results.map((result) => {
                return <AnotherComponent />
            })
        );
    }

    render() {
        const { results } = this.props;
        return (
            <div>
                <ul>
                    {this.renderResults(results)}
                </ul>
            </div>
        )

// in Container Component file
const mapStateToProps = (state, ownProps) => {
    console.log('map state is triggered');
    return {
        results: state.results
    };
}

The Problem

I have found that although my mapStateToProps successfully triggers to update the props to my class component, for some reason the view only updates on the FIRST time that the results array gets manipulated, but does not update on future updates to the store. (Please see update below. This is not entirely accurate.)

I have made sure that this problem is not due to mutating state in my reducers as is often the case; I have confirmed that the mapStateToProps runs every time that the store gets updated (as indicated by the console.log). The problem seems to be between the results props getting returned from mapStateToProps and the view actually rendering the results array, but I do not have visibility to see what Redux is doing under the hood.

The closest problem that someone else has had to what I am experiencing seems to be this, but I do not know how or if this fix applies to my use of a stored array: https://forums.meteor.com/t/the-state-is-correctly-mutated-and-returned-but-view-does-not-rerender/28840/5

Any help would be greatly appreciated. Thank you.


Update

I apologize, but must correct my statement above saying that there are no further updates to the view after the first time the results in the store gets updated. With further testing I have found that is not completely true. The view gets updated only when the results array is sorted according to case 1 of the below sorting function. Cases 2 and 3 are the ones that result in no update to the view. This may/may not be necessary information, but the results array gets sorted by these 3 cases in a cycle onClick in the following order: case 1, case 3, case 2.

// in reducer index.js file
case SORT_RESULTS:
    return {
        ...state,
        results: sortArr(state.results, state.sortType)
    };

// sorting function
function sortArr(arr, sortType) {
    let newArr = [];
    switch (sortType) {
        case '1':
            for (let i = arr.length - 1; i >= 0; i--) {
                newArr.push(arr[i]);
            }
            return newArr;
        case '2':
            newArr = arr;
            newArr.sort((a, b) => {
                return b.num - a.num;
            });
            return newArr;
        case '3':
            newArr = arr;
            newArr.sort((a, b) => {
                let id1 = a.id.toLowerCase();
                let id2 = b.id.toLowerCase();
                if (id1 < id2) {
                    return -1;
                }
                if (id1 > id2) {
                    return 1;
                }
                return 0;
            });
            return newArr;
        default:
            return arr;
    }
}
1
Did you try logging state.results in mapStateToProps?Andrew Li
Yes, I log the state on every dispatch using createLogger() from redux-logger. The results array gets correctly manipulated/updated. Executing console.log(state.results) in mapStateToProps yields the same result.gpsugy
Could you log out componentWillRecieveProps(nextProps) { console.log(nextProps.results) and see if the props are getting updated there?Grandas
componentWillReceiveProps also only runs on the first update to the results storegpsugy
I have updated the post with clarifying information. I am convinced that the problem lies more so with either the way that I am sorting the results array or am returning new state.gpsugy

1 Answers

8
votes

The Solution

Here is the culprit: newArr = arr. Change this to newArr = arr.slice(), and the view will get updated on every sort.

Possible Root Cause

As to why this solution works, I invite anyone else's perspective. Here are my thoughts:

According to https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice:

"The slice() method returns a shallow copy of a portion of an array into a new array object selected from begin to end (end not included)."

Copying the results array using newArr = arr seems to only copy the pointer (to the array), which is really just a reference in memory. If this statement is true, then Redux would not consider the state to have been changed at all because it is comparing the exact same pointer (state.results), even though the actual data of the array itself is confirmed to change by sorting. Copying a completely new array through slice() would involve a new pointer and data, which would be detected as a change to Redux under the hood.