React-redux: Access state props in `mapDispatchToProps`

Created on 1 Nov 2016  路  10Comments  路  Source: reduxjs/react-redux

I was wondering why it is not possible to access state-derived props in mapDispatchToProps. For instance, given the following mapStateToProps function:

const mapStateToProps = (state, props) => {
  return {
    quota: state.quota,
  };
};

I would like to access props.quota in the corresponding mapDispatchToProps:

const mapDispatchToProps = (dispatch, props) => {
  return {
    unlockItem(item_id) {
      if (props.quota > 0) {
        dispatch(unlockItem(item_id));
      }
    },
  };
};

And if it is possible, how. Because I tried and could not. And I could not find anything about it in the documentation.

Most helpful comment

You can't access state in mDTP. But you can in mergeProps, so your connect would look something like:

connect(
  state => ({ quota: state.quota }),
  { unlockItem }.
  (stateProps, dispatchProps, ownProps) => ({
    ...ownProps,
    ...stateProps,
    ...dispatchProps,
    unlockItem(item_id) {
        if (stateProps.quota > 0) {
          dispatchProps.unlockItem(item_id)
        }
    }
  })
)(YourComponent)

I myself typically use recompose library to do is a slightly different way. I'd do:

compose(
  connect(
    state => ({ quota: state.quota }),
    { unlockItem }
  ),
  withHandlers({
    unlockItem: => props => item_id => {
      if (props.quota > 0) {
        props.unlockItem(item_id);
      }
    }
  })
)(YourComponent)

One of the advantages of withHandlers is that it passes the same method to YourComponent every re-render, allowing it to optimize out of re-renders if it implements shouldComponentUpdate.

All 10 comments

You can't access state in mDTP. But you can in mergeProps, so your connect would look something like:

connect(
  state => ({ quota: state.quota }),
  { unlockItem }.
  (stateProps, dispatchProps, ownProps) => ({
    ...ownProps,
    ...stateProps,
    ...dispatchProps,
    unlockItem(item_id) {
        if (stateProps.quota > 0) {
          dispatchProps.unlockItem(item_id)
        }
    }
  })
)(YourComponent)

I myself typically use recompose library to do is a slightly different way. I'd do:

compose(
  connect(
    state => ({ quota: state.quota }),
    { unlockItem }
  ),
  withHandlers({
    unlockItem: => props => item_id => {
      if (props.quota > 0) {
        props.unlockItem(item_id);
      }
    }
  })
)(YourComponent)

One of the advantages of withHandlers is that it passes the same method to YourComponent every re-render, allowing it to optimize out of re-renders if it implements shouldComponentUpdate.

On the flip side, I personally tend to write a method on the component that explicitly takes a value from props and calls a bound action creator:

onDoSomeThingClicked() {
    const {someItemID} = this.props;
    this.props.doSomeThing(someItemID);
}

Also, to better answer your question: while I don't have a specific reference off the top of my head, I _think_ the main reason state isn't available in mapDispatch is to improve perf. Otherwise, you'd potentially be re-creating functions every time an action was dispatched and the store updated.

Great answers folks! Lots of alternative ways to deal with it. And now I understand the reasons mentioned regarding performance. Thanks!

Another solution:

<div onClick={()=>this.props.onMyAction(this.state)}>MyAction</div>

```js
function mapDispatchToProps(dispatch, ownProps) {
return {
onMyAction: (state) => {
dispatch(createAction(state.actionType));
}
}
}
````

@msangel If you must, I would suggest providing only the specific properties of the state-props that you need in the dispatch-prop function. Also, keep in mind we're referring to state-props from react-redux, and not component state, as you have written this.state.

<div onClick={() => this.props.onMyAction(this.props.actionType)}>MyAction</div>

function mapDispatchToProps(dispatch, ownProps) {
  return {
    onMyAction: (actionType) => {
      dispatch(createAction(actionType))
    }
  }
}

or if you prefer named args

<div onClick={() => this.props.onMyAction({ actionType: this.props.actionType })}>MyAction</div>

function mapDispatchToProps(dispatch, ownProps) {
  return {
    onMyAction: ({ actionType }) => {
      dispatch(createAction(actionType))
    }
  }
}

What I do is having my action accept a new value to change the state, but in case I pass a function I call that function passing the state as parameter (local state in this example because I use a pointer like path but is not relevant here)

CHANGE_SELECTED_ELEMENT_PROPERTY :(state, {newValue ,propertyName = ""}) => {
      state.setPropertyDot(propertyName, typeof newValue === "function" ? newValue(state) : newValue);
     return state;
    },

const mapDispatchToProps = (dispatch,ownProps) => {
  return {
    onAddOperation: (e) => {
      return dispatch({
        type: "CHANGE_SELECTED_ELEMENT_PROPERTY",
        payload: {
          newValue: (state) => { 
            return {baseValue : state.getCurrentElement().getPropertyDot(ownProps.propertyName), 
                    operations : []
                  }
              },
          propertyName: ownProps.propertyName
        }
      })
    }
  }
}

hope this help

@Raising Thanks for sharing!

I would avoid passing a complex object such as a function, or anything that cannot be serialized to a JSON string. In my experience, there is rarely/never a need for this pattern, and you are only setting yourself up for incompatibilities in the future, not to mention debugging headaches from day 1.

I would also claim, ideally, your reducers should be pure functions that do not rely on any external function calls such as you have.

Out of curiosity though, have you encountered a situation where none of the other proposed solutions have worked for you, and you 100% had to pass a function in the action payload?

Thanks again!

@TSMMark THanks for answering!

I think you are right and my aproach may be easy at first but will lead to problems in the future.

I have used this only on two spots in my project so I'll find another solution to prevent being unable to serialice the actions.

Thanks for your insight!!

@TSMMark

I was resolving this problem and end up adding a bit extra of functionality.
Each action before it pased to the reducer ( and saved in the history) has its payload preprocesed checking they first level props and in case of function executing it. so in the end the action is translated and I keep the power of accesing the state and manipulating the payload using a function ( this will reduce the amount of diferents action I need.)

the code looks like this.

export default (state = defaultState, action) => {
state = Object.assign(Object.create(selectors),state);

  for (let reducer of reducerCluster){
    if (reducer.actions[action.type]){
      let stateNodePath = reducer.getStateNode(state,action);
      let stateNode = state.getPropertyDot(stateNodePath);

      **action.payload = processPayload(stateNode,action.payload);** 
      state.setPropertyDot( stateNodePath, reducer.actions[action.type](stateNode, action.payload ));    
    }
  }
  saveAction(action);
    return state;
}


const processPayload = (state,actionPayload ) => {
  for ( let key in actionPayload){
    if ( typeof actionPayload[key] === "function" ){
      actionPayload[key] = actionPayload[key](state);
    }
  }
  return actionPayload;
}

thanks you, you inspirated me to improve

Was this page helpful?
0 / 5 - 0 ratings