简体   繁体   中英

React/Redux controlling when a functional component renders

I'm having this problem where my app updates too much. In my reducer I have an action of interest checkTasks . It doesn't do much, checkTasks goes through my list of current tasks, if any of them is expired, it removes them from the tasks array to place them into the expiredTasks array. My problem is that most of the time, checkTasks doesn't do anything, only sometimes. But every time checkTasks gets called my component gets rendered.

The mapStateToProps of my component looks like this:

const mapStateToProps = (_state) => {
    return {
        tasks: _state.tasksState
    }
}

and my component cares about 2 attributes from the state:

function Tasts({ tasksState: _tasksState}){
    ...
    return <>
        {renderExpired(_tasksState.expiredTasks)}
        {renderTasks(_tasksState.tasks)}
    </>
}

reducer

const reducer(_state = { tasks: [], expiredTasks: [] }, _action){
    const newState = { ..._state };
    ...
    case 'checkTasks':
            for (let i = newState.tasks.length - 1; i >= 0; i--) {
                if (isExpired(newState.tasks[i])) {
                    newState.expiredTasks.push(newState.tasks.splice(i, 1)[0]);
                }
            }
        break;

    ...

    return newState;
}

What I'm noticing is that every time (once per second) the checkTasks action gets called, my component rerenders, even if practically the information in expiredTasks and tasks hasn't changed.

If I could change my mapStateToProps to something like

const mapStateToProps = (_state) => {
    return {
        tasks: _state.tasksState.tasks,
        expiredTasks: _state.tasksState.expiredTasks
    }
}

That would probably stop the constant refreshes, however, that seems to break all refreshes. Including tasks moving from one array to the other. I don't know if there's a way to tell redux NOT to trigger an update from the reducer, although that's probably an anti-pattern. I'm also trying not to go trough shouldComponentUpdate given that evaluating whether I should update may require to check 2 object arrays against each other. Not triggering an update in the first place is much easier to handle than having to compare multiple arrays.

So if you're using redux-thunk, you can really enhance the logic inside your action-creators. In this case, we're going to check to see if any tasks have expired by actually pulling your reducer data into our action-creator. Then applying very similar logic to what you already are doing, we will decide whether or not we will actually dispatch a new action.

const checkTasks = () => {
    return (dispatch, getState) => {
       const currentState = getState().yourReducer //your reducer key goes here. Returns its current state
       const expiredTasks = [] 

       for(let i = currentState.tasks.length - 1; i >= 0; i--){
          if(isExpired(currentState.tasks[i]){ //remember to import your isEmpty() logic
               expiredTasks.push(currentState.tasks[i])
          }
       }

       if(expiredTasks.length > 0){
          dispatch({
             type: "checkTasks"
          })
       }

   }
}

If expiredTasks are empty then we won't send any action. No action, means no reducer update. Which means no component re-rendering.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM