Skip to content

let action creators be passed via the provider #249

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

Closed
ianstormtaylor opened this issue Jan 14, 2016 · 5 comments
Closed

let action creators be passed via the provider #249

ianstormtaylor opened this issue Jan 14, 2016 · 5 comments

Comments

@ianstormtaylor
Copy link

First off, thanks so much for Redux and the amazing tooling and introductory content around it. It's been awesome to learn how all of the parts fit together.

I've been running into an issue while building out my first foray into a React/dux app dealing with action creators. I'm having to use mapDispatch a ton for just passing the same action creators into the props of each of my top-level components (without React Router I end up having around 5 top-level components so far, which I've called "containers" following the examples I've seen).

And the mapDispatch that I'm writing is often just a super plain object like { loadUser, loadTeam, loadCollection } that maps keys to keys. It's getting really boilerplate-y to have to keep doing this all the time, when really I'd just like to have all my action creators already bound to my single store.

It would be amazing to be able to pass them into the <Provider>:

<Provider store={store} actions={actions}>
  <App />
</Provider>

And be able to use that inside components as:

componentWillMount() {
  const { actions } = this.props
  actions.loadUser(...)
  actions.loadTeams(...)
  ...
}

From looking through the issues, I found that I'm not the first to suggest this, which means others are grappling with this problem too. Here are a few that mention discuss similar things:

One time when it was brought up, it was discarded because of not wanting to pollute props with all of the actions, since the "clobber likelihood" would be high. I totally get the concern about not wanting to pollute props with tons of actions, but we can avoid that by nesting them in props.actions instead.

Another time, @gaearon mentioned the (important) needs of code splitting. I think that's definitely a use case to support, but surely we could have a load-later approach that works very similarly to the current mapDispatch that would facilitate loading extra modules, while also making it easy for the main use case?

Curious to hear everyone's thoughts.

From my initial use of both React and Redux, this would save me boilerplate code, and make it possible for people to write nicer boilerplate-reducing functionality on top of the base libraries.

@gaearon
Copy link
Contributor

gaearon commented Jan 14, 2016

I don't understand what is boilerplatey about specifying a single-line function several times. Can you give specific examples?

@ianstormtaylor
Copy link
Author

Absolutely! In my project so far, I have 5 container components, all of which need to "connected". And I haven't fleshed out the entire thing yet, so that number will grow for sure. For each one I have something along the lines of:

import { loadCollection, loadItem, loadTeam } from '../../actions'

// ...

function loadData(props) {
  const { loadCollection, loadItem, loadTeam } = props
}

// ...

const mapDispatchToProps = {
  loadCollection,
  loadItem,
  loadTeam
}

// ...

const Connected = connect(mapStateToProps, mapDispatchToProps)(Component)

But just 3 actions for a container isn't a lot; I expect some of them to need 10 or more. And each time I add or change an action, I have to make sure to update it in all of these places, or things break in ways that are sometimes hard to discern.

For example, I forgot to add loadTeam to mapDispatchToProps since it was at the bottom of the file, and then proceeded to be pretty confused since the reference still existed, it just wasn't to a "bound" action creator, so the error was non-obvious. Of course I could rewrite my import to make that not possible:

import * from '../../actions'

// ... but then I have to make my dispatch mapping more verbose ...

const mapDispatchToProps = {
  loadCollection: actions.loadCollection,
  loadItem: actions.loadItem,
  loadTeam: actions.loadTeam
}

If I actually gained from the configuration I might understand why it needed to be verbose, but for my own developer experience, the ideal for me would be to not have to worry about these kinds of things, and just have any function I exported from * as actions be bound to the store automatically. That way, even though I understand how it's all wired up, I'd never have to think about the mappings again.

Instead, I'd get a really simple workflow of: write an action creator function, and then go use it in my container components. Ideally as easily as just two things to think about:

export function loadCollection(id) {}

// ...

const { actions } = this.props
actions.loadCollection(id)

If I was able to write this in myself from the top-level, I'd be more than glad. But it seems like the way the system is setup, there's no way to pass other information into the <Provider> instance, so there's no way I can do it without creating a singleton, which I want to avoid. (I may be missing a good trick here though—I guess I could attach bound actions to the store.dispatch function if I really wanted to, but that seems real hacky.) I've even looked into writing a different <Provider>, but there's so much logic in it that would need to be duplicated and kept up to date that it would be a huge pain to do.

Let me know if that all makes sense, or if I'm doing something incorrectly. Happy to discuss more. Thanks!

@maxguzenski
Copy link

You can provide you own connect, and use reselect to memoize it.

import { createSelector } from 'reselect'
import { bindActionCreators } from 'redux'
import { connect as reduxConnect } from 'react-redux'
import { * as myActions } from './actions'

// bindActionCreators will be execute ONLY on first time 
//   or when dispatch instance changes
const mapDispatchToProps = createSelector(
  dispatch => dispatch,
  dispatch => ({actions: bindActionCreators(myActions, dispatch)})
)

const connect = (mapStateToProps, mergeProps) => {
  return reduxConnect(mapStateToProps, mapDispatchToProps, mergeProps)
}

// use this connect on yours Components
export default connect

With this you can provide many personalized connects as you want:

  • connectStores('store1', 'store2')
  • connectCurrentUser()

@gaearon
Copy link
Contributor

gaearon commented Jan 14, 2016

Please take a close look at all examples in the docs. What you want is either

import * as actions from '../actions';

export default connect(
  mapStateToProps,
  actions
)(MyComponent);

// this.props.loadData()

or

import * as actions from '../actions';
import { bindActionCreators } from 'redux';

const mapDispatchToProps = (dispatch) => {
  return {
    actions: bindActionCreators(actions, dispatch)
};

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(MyComponent);

// this.props.actions.loadData()

There's no need to explicitly enumerate them.

By the way you can totally write your own Provider. Not sure what logic duplicate you are concerned about because there is almost no logic there—it just puts store in context and that's it.

@gaearon gaearon closed this as completed Jan 14, 2016
@ianstormtaylor
Copy link
Author

Thanks for the response again. I actually did look at all of the examples before asking.

Both solutions solve the problem partially, but still require that the boilerplate in every file, even though I know I want every "connected" component to have access to all of the bound action creators, just like they have access to the store. Which means not only is it impossible for me remove the boilerplate completely (like with store passing), but it means that it's impossible to pave over react-redux simply for this case from another library. It has to be done in the end files.

I could create a separate <Provider>, which is pretty lightweight, but it seems like I'd also need implement a separate connect() as well, which has tons of extra logic that I'd need to carefully pass through, or reimplement, so maintaining it would be very non-trivial.

It seems like adding all of the bound action creators onto store.dispatch as properties is the only way to do it from the top-level without having to repeat in each file, so I'll go with that, even if it feels a bit hackish. Thanks

foiseworth pushed a commit to foiseworth/react-redux that referenced this issue Jul 30, 2016
Remove React-specific code in favor of gaearon/redux-react
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants