-
-
Notifications
You must be signed in to change notification settings - Fork 3.4k
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
Add state
as the third parameter to mapDispatchToProps
#237
Comments
I agree. For full generality, |
Well now the implementation is very efficient. It will not run AFAIK adding |
That makes sense to me, I've glanced at the implementation of |
I don't think async logic belongs in the map functions at all. They should be only pure functions which transforms the state to props and binds action creators to dispatch. Which means you cannot modify the state during the map state execution so getState would always just return the same state. Also if you could modify the state from the map state functions it could lead to infinite loops because every update would reinvoke the map functions. Async logic goes into action creators and middleware. See http://redux.js.org/docs/advanced/index.html On the side note I think react-redux should explicitly deny invoking the |
If you take a more closer look at the implementation of https://github.com/rackt/react-redux/blob/caae2e1/src/components/connect.js#L214-L219 you see that the map state functions are called during render. Would start an async operation from render method of a component? |
Sure, I think that's one opinion, but I don't take that as gospel. In the paradigm you describe, you redux-saga puts the asynchrony in long-running generators in a side band. They still hook into the An alternative I've been experimenting with uses vanilla Redux's pure, synchronous action creators (before async is introduced in the guide) and an middleware-free For asynchrony, you give functions that live out of that flow a callback to I don't see my approach as mutually exclusive with something like redux-saga. The latter sounds like a smart approach to having long-running processes outside of the UI that share state with the UI and can generate changes, like websocket listeners, automation, etc. But for stereotypical MVC controller logic, like bread-and-butter input/output, I think my approach is conceptually simpler and cleaner. |
The proposed "reactions" approach actually works differently than I first reported. My current understanding is that it uses the store to represent effects that should happen and the state of their consequences. Then, a new reactions stage realizes those effects. This stage runs after the reducers produce the next state. So, for example, an API call needs to be made. Dispatching the action that should trigger it sets an "effect" value in the state, which expresses that "data should be getting fetched right now from When the XHR completes, its continuation would dispatch an action that should have the result of removing that fetch effect from the state. This approach is intriguing because it refies the state of ongoing process in the store. So you could serialize the state of your application down to having a specific async fetch in progress, and rehydrate it in that exact state. Wild! |
var ToggleFollowButton = ({toggleFollow}) => <button onClick={toggleFollow}>Toggle</button>;
ToggleFollowButton = connect(
({ postsFollowing }, { id }) => ({
isFollowing: postsFollowing[id]
}), (dispatch) => ({
dispatch
}), ({ isFollowing }, { dispatch }, { id, ...otherProps }) => ({
...otherProps,
toggleFollow: isFollowing ?
() => dispatch(unfollowPostActionCreator(id))) :
() => dispatch(followPostActionCreator(id)))
})
})(ToggleFollowButton) Or, in a more readable way: I think adding |
Same example in a less terse way: function mapStateToProps(state, ownProps) {
return {
isFollowing: state.postsFollowing[ownProps.id]
};
}
function mergeProps(stateProps, dispatchProps, ownProps) {
const { isFollowing } = stateProps;
const { dispatch } = dispatchProps;
const { id } = ownProps;
const toggle = isFollowing ?
unfollowPostActionCreator :
followPostActionCreator;
return {
...ownProps,
toggleFollow: () => dispatch(toggle(id)))
};
}
ToggleFollowButton = connect(
mapStateToProps,
null,
mergeProps
})(ToggleFollowButton) |
Please take a look at #1. This option has been considered and rejected. Yes, it's very bad for performance to re-bind action creator on every dispatch, which is what will happen if we let people access the state in the same place they bind action creators. Technically they can still do it now with |
Absolutely correct, this is a performance nightmare scenario where nothing really changes but bound functions kill the performance optimizations. In fact it's so bad I don't recommend what I wrote above. Bind action creators as rarely as possible, and prefer to not bind them to particular IDs. Instead, just let your components call appropriate props with appropriate arguments: handleClick() {
if (this.props.isFollowing) {
this.props.unfollow(this.props.id);
} else {
this.props.follow(this.props.id);
}
} This is not as elegant but it's much more performant and also easier to understand than a complex I'm closing this because I think the two workarounds I offered above will suffice. If you believe React Redux API is not good enough please create another library that better fulfills your goals. The goal of this library is to encourage performant patterns because otherwise people will say “Redux is slow!” even if the cause is their suboptimal function binding code. We don’t want this to happen, so we’d rather make certain unperformant cases harder to implement. |
@gaearon Thanks for the info. With that taken into consideration, I still think having react-redux is a great API, and I hope my comments haven't come off to the contrary. But I hope you understand that it makes more sense to try to advocate for ideas in an existing library than to maintain a fork or entirely separate product. I realize that a great deal of thought has been put into this already, so no disrespect intended to the work that's already been done. I haven't read #1 yet, but I definitely will, because I want to present productive ideas that haven't already been beaten to death. |
I'm hesitant adding it because people might get the idea that |
Oh yeah. Somehow I didn't realize
Yep, this what I'm actually doing now. It just makes me bit sad that the class based version is actually more performant and I maybe even more terse than the functional version. |
The documentation only states the thing you you say you don't recommend here. Update it perhaps? For reference, the sentence I'm referring to (emphasis mine):
|
People asked for this, so I added this API. That I don't recommend doing this is my personal opinion. Most projects can do this just fine; it's only a problem when you want to squeeze that last bit of performance. Feel free to PR the docs to make them more nuanced! |
@gaearon Dan, I wonder, the default
But when nothing really changed, the default |
Yeah, I'm guessing he was referring to the idea that the component passed to |
So that I could do this:
Now I'm fully aware that I can do this in the component code by passing the "follow state" and both action creators to it, but when using function components this would be more performant way to do this. In normal class based component I can do this only once as a method but with a function component I lose some performance because the toggle function is recreated on every render. Also when using
react/jsx-no-bind
eslint rule it's hard to avoid the warning.Another point is that if the toggle function is recreated on every render it can interfere with PureRenderMixin optimized components further down in the component tree.
Another option would be to add
dispatch
tomapStateToProps
as the third argument. Which actually could be prettier because then the object shorthand formapDispatchToProps
would be still available.EDIT: Also it would be easier to implement because the reinvoke semantics of
mapDispatchToProps
would not change. So I'll actually propose this version :)I can also send a PR if we see this as something we want.
The text was updated successfully, but these errors were encountered: