Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Access state props in mapDispatchToProps #535

Closed
gnapse opened this issue Nov 1, 2016 · 10 comments
Closed

Access state props in mapDispatchToProps #535

gnapse opened this issue Nov 1, 2016 · 10 comments

Comments

@gnapse
Copy link

gnapse commented Nov 1, 2016

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.

@jimbolla
Copy link
Contributor

jimbolla commented Nov 1, 2016

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.

@markerikson
Copy link
Contributor

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);
}

@markerikson
Copy link
Contributor

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.

@gnapse
Copy link
Author

gnapse commented Nov 1, 2016

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

@msangel
Copy link

msangel commented Jan 12, 2018

Another solution:

<div onClick={()=>this.props.onMyAction(this.state)}>MyAction</div>
function mapDispatchToProps(dispatch, ownProps) {
        return {
            onMyAction: (state) => {
                dispatch(createAction(state.actionType));
            }
        }
    }

@TSMMark
Copy link

TSMMark commented Jan 12, 2018

@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))
    }
  }
}

@Raising
Copy link

Raising commented Jan 15, 2018

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

@TSMMark
Copy link

TSMMark commented Jan 15, 2018

@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!

@Raising
Copy link

Raising commented Jan 18, 2018

@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!!

@Raising
Copy link

Raising commented Jan 26, 2018

@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

@reduxjs reduxjs locked as resolved and limited conversation to collaborators Mar 9, 2018
@reduxjs reduxjs deleted a comment from mileschristian Mar 9, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants