React / Redux / Reselect - is mapped state to prop

2019-08-22 02:29发布

问题:

In our project we are using react-redux with reselect and redux-saga

In the store I have a selectedStageId along with an array of stages and a memoized selector that finds and returns the correct stage based on the id.

This selector is mapped to one of my components props as this.props.selectedStage, and in one of my onClick handlers, I dispatch an action to update the selectedStageId to the id of the newly selected UI item, and then attempt to pass the new selectedStage to an edit method...

However, even though I have added breakpoints and verified that both my reducers and selectors are being called with the new id, the new selectedStage value is NOT being updated synchronously on my component's props... instead this.props.selectedStage still references the previous value...

Example:

onItemSelect = (stageId: number): void => {

  // updateSelectedStageId() is a mapped dispatch method that updates
  // selectedStageId in the store

  this.props.updateSelectedStageId(stageId);

  // I have debugged the above dispatched action and verified that
  // both the reducer and selector are being called with the new
  // stageId, and the selector is executing and returning the correct
  // new stage object before the next line is processed...

  this.editStage(this.props.selectedStage;); // <-- wrong value

  // this.props.selectedStage is the mapped memoized selector that
  // returns the target stage based on the selectedStageId, but
  // here it still references the previous value, not the new one
}

I've read that redux is normally synchronous, but that redux-saga could somehow be making this asynchronous... However, since I've verified that both the reducer and selector are being called synchronously, it seems that the issue lies somewhere else, maybe in when connect actually updates mapped state to props...

Can anyone provide some insight on how to accomplish this the right way?

Thanks!

Josh

UPDATE

Here are the related bits for my reducer and selector...

reducer.ts

import produce from 'immer';

const initialState = {
  selectedStageId: -1,
  stages: []
}

export const appReducer = (state = initialState, action) => {
  return produce(state, (draftState) => {
    switch (action.type) {
      case ActionType.UpdateSelectedStageId
        draftState.selectedStageId = action.id;
        break;
    }
  });
}

selectors.ts

import { createSelector } from 'reselect';

const _getSelectedStage = (stages, id) => {
  return stages.find((s) => s.id === id);
};
export const selectedStage = createSelector(
  [getStages, getSelectedStageId],
  _getSelectedStage
);

回答1:

I had the same problem (almost the same actually) today.

Every state's change (redux or not) are asynchronous. You can't actually trust the value will be updated.

Triggering a synchronous action doesn't mean it's synchronous within the current rendering phase. So your app dispatch the action, finishes the rendering, then the new redux state triggers a re-render.

dispatch({type: CHANGE_VALUE, value : newValue})
console.log(value) //oldValue

The state get's updated synchronously, but React will only be aware of that change in the next render.

So, just use the value you are dispatching the action:

onItemSelect = (stageId: number): void => {

  this.props.updateSelectedStageId(stageId);

  this.editStage(stageId)

}