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.
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
Most helpful comment
You can't access state in mDTP. But you can in mergeProps, so your
connectwould look something like:I myself typically use
recomposelibrary to do is a slightly different way. I'd do:One of the advantages of
withHandlersis that it passes the same method toYourComponentevery re-render, allowing it to optimize out of re-renders if it implements shouldComponentUpdate.