简体   繁体   中英

Redux: Calling store.getState() in a reducer function, is that an anti pattern?

I'm wondering, sometimes I have a reducer that needs information from another reducer. For example I have this reducer:

import * as ActionTypes from '../actions/action_type_constants';
import KeyCode from 'keycode.js/index';
import {store} from "../index";
import {mod} from "../pure_functions";

export function selectedCompletion(state = 0, action) {
  if (action.type === ActionTypes.arrowKeyPressed) {
    const completionsLength = store.getState().completions.data.length;
    if (action.keyCode === KeyCode.UP) {
      return mod(state - 1, completionsLength);
    } else if (action.keyCode === KeyCode.DOWN) {
      return mod(state + 1, completionsLength);
    }
  }
  return state;
}

I do call store.getState at the second line of the function, because otherwise I can not determine the index correctly.

I could probably refactor this and the other reducer, so that it becomes one big reducer, but for readability I would prefer this option.

I'm not sure if I would get somehow into problems if I use this pattern of calling store.getState() in a reducer.

Yes, this is absolutely an anti-pattern. Reducer functions should be "pure", and only based on their direct inputs (the current state and the action).

The Redux FAQ discusses this kind of issue, in the FAQ on sharing state between reducers . Basically, you should either write some custom reducer logic that passes down the additional information needed, or put more information into your action.

I also wrote a section for the Redux docs called Structuring Reducers , which discusses a number of important concepts related to reducer logic. I'd encourage you to read through that.

The pattern you want is a case of composition because you are preparing new state based in other existing states from other domain (in the sense of reducer domains). In the Redux documentation an example is provided under the topic entitled Computing Derived States .

Notice that their sample, however, combined the states in the container - not in the reducer; yet feeding the component that needs it.

For these coming to this page wondering how to upgrade their large applications to redux 4.0 without revamping their complete statemanagement because getState etc were banned in reducers:

While I, like the authors, dissaprove of that antipattern, when you realize that they banned the usage of these functions without this without any technical reasons and without opt-out, leaving people without updates that have the misfortune of having codebases which broadly use this antipattern... Well, I made a merge request to add an opt-out with heavy guards, but it was just immediately closed.

So I created a fork of redux, that allows to disable the bans upon creating the store:

https://www.npmjs.com/package/free-redux

For anyone else, use one of the examples here or the way I provided in another question :

An alternative way, if you use react-redux and need that action only in one place OR are fine with creating an HOC (Higher oder component, dont really need to understand that the important stuff is that this might bloat your html) everywhere you need that access is to use mergeprops with the additional parameters being passed to the action:

const mapState = ({accountDetails: {stateOfResidenceId}}) => stateOfResidenceId;

const mapDispatch = (dispatch) => ({
  pureUpdateProduct: (stateOfResidenceId) => dispatch({ type: types.UPDATE_PRODUCT, payload: stateOfResidenceId })
});

const mergeProps = (stateOfResidenceId, { pureUpdateProduct}) => ({hydratedUpdateProduct: () => pureUpdateProduct(stateOfResidenceId )});

const addHydratedUpdateProduct = connect(mapState, mapDispatch, mergeProps)

export default addHydratedUpdateProduct(ReactComponent);

export const OtherHydratedComponent = addHydratedUpdateProduct(OtherComponent)

When you use mergeProps what you return there will be added to the props, mapState and mapDispatch will only serve to provide the arguments for mergeProps. So, in other words, this function will add this to your component props (typescript syntax):

{hydratedUpdateProduct: () => void}

(take note that the function actually returns the action itself and not void, but you'll ignore that in most cases).

But what you can do is:

const mapState = ({ accountDetails }) => accountDetails;

const mapDispatch = (dispatch) => ({
  pureUpdateProduct: (stateOfResidenceId) => dispatch({ type: types.UPDATE_PRODUCT, payload: stateOfResidenceId })
  otherAction: (param) => dispatch(otherAction(param))
});

const mergeProps = ({ stateOfResidenceId, ...passAlong }, { pureUpdateProduct, ... otherActions}) => ({
  ...passAlong,
  ...otherActions,
  hydratedUpdateProduct: () => pureUpdateProduct(stateOfResidenceId ),
});

const reduxPropsIncludingHydratedAction= connect(mapState, mapDispatch, mergeProps)

export default reduxPropsIncludingHydratedAction(ReactComponent);

this will provide the following stuff to the props:

{
  hydratedUpdateProduct: () => void,
  otherAction: (param) => void,
  accountType: string,
  accountNumber: string,
  product: string,
}

On the whole though the complete dissaproval the redux-maintainers show to expanding the functionality of their package to include such wishes in a good way, which would create a pattern for these functionalities WITHOUT supporting fragmentation of the ecosystem, is impressive.

Packages like Vuex that are not so stubborn dont have nearly so many issues with people abusing antipatterns because they get lost, while supporting a way cleaner syntax with less boilerplate than you'll ever archive with redux and the best supporting packages. And despite the package being way more versatile the documantation is easier to understand because they dont get lost in the details like reduxs documentation tends to do.

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