javascript – How to access state inside Redux reducer?-ThrowExceptions

Exception or error:

I have a reducer, and in order to calculate the new state I need data from the action and also data from a part of the state not managed by this reducer. Specifically, in the reducer I will show below, I need access to the accountDetails.stateOfResidenceId field.

initialState.js:

export default {
    accountDetails: {
        stateOfResidenceId: '',
        accountType: '',
        accountNumber: '',
        product: ''
    },
    forms: {
        blueprints: [

        ]
    }
};

formsReducer.js:

import * as types from '../constants/actionTypes';
import objectAssign from 'object-assign';
import initialState from './initialState';
import formsHelper from '../utils/FormsHelper';
export default function formsReducer(state = initialState.forms, action) {
  switch (action.type) {
    case types.UPDATE_PRODUCT: {
        //I NEED accountDetails.stateOfResidenceId HERE
        console.log(state);
        const formBlueprints = formsHelper.getFormsByProductId(action.product.id);
        return objectAssign({}, state, {blueprints: formBlueprints});
    }

    default:
      return state;
  }
}

index.js (root reducer):

import { combineReducers } from 'redux';
import accountDetails from './accountDetailsReducer';
import forms from './formsReducer';

const rootReducer = combineReducers({
    accountDetails,
    forms
});

export default rootReducer;

How can I access this field?

How to solve:

I would use thunk for this, here’s an example:

export function updateProduct(product) {
  return (dispatch, getState) => {
    const { accountDetails } = getState();

    dispatch({
      type: UPDATE_PRODUCT,
      stateOfResidenceId: accountDetails.stateOfResidenceId,
      product,
    });
  };
}

Basically you get all the data you need on the action, then you can send that data to your reducer.

###

Your options are to either write more logic besides just use of combineReducers, or include more data in the action. The Redux FAQ covers this topic:

https://redux.js.org/faq/reducers/

Also, I’m currently working on a new set of pages to the Redux docs on the topic of “Structuring Reducers”, which you may find helpful. The current WIP pages are at https://github.com/markerikson/redux/blob/structuring-reducers-page/docs/recipes/StructuringReducers.md .

###

I’m not sure if this approach is an anti-pattern but it worked for me. Use a curried function in your actions.

export const myAction = (actionData) => (dispatch, getState) => {
   dispatch({
      type: 'SOME_ACTION_TYPE',
      data: actionData,
      state: getState()
   });
}

###

It is simple to write your own combine function that does exactly what you want:

import accountDetails from './accountDetailsReducer';
import forms from './formsReducer';

const rootReducer = (state, action) => {
        const newState = {};

        newState.accountDetails = accountDetails(state.accountDetails, action);
        newState.forms = forms(state.forms, action, state.accountDetails);

        return newState;
    };

export default rootReducer; 

Your FormReducer would then be:

export default function formsReducer(state = initialState.forms, action, accountDetails) {

The formsReducer now has access to the accountDetails.

The benefit of this approach is that you only expose the slices of state the you need, instead of the entire state.

###

I sugguest that you pass it into the action creator.
So someplace you’ll have an action creator that does something like this:

updateProduct(arg1, arg2, stateOfResidenceId) {
  return {
    type: UPDATE_PRODUCT,
    stateOfResidenceId
  }
}

At the place where you trigger the action, assume you are using react, you can use

function mapStateToProps(state, ownProps) {
  return {
    stateOfResidenceId: state.accountdetails.stateOfResidenceId
  }  
}

and connect to your react component using the react-redux’s connect.

connect(mapStateToProps)(YourReactComponent);

Now, in your react component where you trigger the action updateProduct, you should have the stateOfResidenceId as a prop, and you can pass it to your action creator.

It sounds convoluted, but it is really about separation of concerns.

###

You can try to use:

redux-named-reducers

Which allows you to get state anywhere in your code like so:

const localState1 = getState(reducerA.state1)
const localState2 = getState(reducerB.state2)

But think first if it would be better to pass the external state as a payload in the action.

###

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.

###

While dispatching an action, you can pass a parameter. In this case, you could pass accountDetails.stateOfResidenceId to the action and then pass it on to the reducer as payload.

Leave a Reply

Your email address will not be published. Required fields are marked *