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

Could mapStateToProps be an object? #1048

Closed
selbekk opened this issue Oct 16, 2018 · 13 comments
Closed

Could mapStateToProps be an object? #1048

selbekk opened this issue Oct 16, 2018 · 13 comments

Comments

@selbekk
Copy link
Contributor

selbekk commented Oct 16, 2018

I'm playing around with selectors, and most of my mapStateToProps functions look like this:

const mapStateToProps = state => ({
  isAuthenticated: selectors.isAuthenticated(state),
  isSomethingElse: selectors.isSomethingElse(state),
});

Since I always pass on the state, would it be okay to do the same thing as mapDispatchToProps does, and just allow sending an object of functions? Like so:

const mapStateToProps = {
  isAuthenticated: selectors.isAuthenticated,
  isSomethingElse: selectors.isSomethingElse,
};

Then, connect could do the work required to call each of these with the state.

I'd be happy to submit a PR doing this - but what are your thoughts? I'm sure there's a reason the API is the way it is :)

@timdorr
Copy link
Member

timdorr commented Oct 16, 2018

It makes the logic around running selectors much more complex. We would have to iterate through all the keys and check for shallow equality there, which could be slow. Also, it becomes more confusing for users. Would this be expected to work?

const mapStateToProps = {
  isAuthenticated: selectors.isAuthenticated,
  foo: {
    isSomethingElse: selectors.isSomethingElse,
  }
}

I don't think so, but I could see someone being confused or tripped up by the way they import things.

It's significantly simpler for us to implement this as just a pure function and also simpler to present to users as an API. So, I don't think we'll add something like this.

@timdorr timdorr closed this as completed Oct 16, 2018
@selbekk
Copy link
Contributor Author

selbekk commented Oct 16, 2018

Fair enough :-)

@markerikson
Copy link
Contributor

We had a potential PR over at #724 that sat around for a while before we finally closed it. There's a lot of potential edge cases around the "factory function" syntax that mapState supports, and eventually it became more complex than we wanted to deal with.

@selbekk
Copy link
Contributor Author

selbekk commented Oct 16, 2018

Understandable! Thanks for taking the time to explain! :-)

@jchook
Copy link

jchook commented Jan 19, 2019

I also wondered about this. Seemed lopsided to have mapDispatchToProps accept an object, but not mapStateToProps.

@selbekk I guess we could use a function like this:

const bindSelectors = (selectors) => (state) => {
  const props = {}
  Object.keys(selectors).forEach(key => {
    props[key] = selectors[key](state)
  })
  return props
}

Naively seems like a PR could essentially get by with:

if (isPlainObject(mapStateToProps)) {
  mapStateToProps = bindSelectors(mapStateToProps)
}

@markerikson
Copy link
Contributor

I'm willing to potentially reconsider this as long as we keep it narrowly scoped in behavior and implementation.

No factory functions. No nesting.

I can see two useful forms:

  • An array of string key names
  • An object of selectors

Both are doable if we keep the behavior limited to basically what @jchook showed. Question is, whether there's enough benefit for including that functionality.

Tweeted this question: https://twitter.com/acemarke/status/1087568080052596736

I'll reopen this issue for the sake of discussion.

@markerikson markerikson reopened this Jan 22, 2019
@manuelbieh
Copy link

Would love to see a shorthand notation where I can just pass an object of selectors. It can be so cumbersome to pass state to every single selector over and over again.

@timdorr
Copy link
Member

timdorr commented Jan 22, 2019

I really really don't want to do this. Our selector code is complex enough as it is. Just write a combineSelectors function and leave it at that. This API is confusing enough as it is.

@jchook
Copy link

jchook commented Jan 22, 2019

Tim, do you think it would be worse than adding whenMapStateToPropsIsAnObject()?

Again, forgive my naive perspective, but I see we have:

mapDispatchToProps.js

mapStateToProps.js

Can we simply add this "missing" factory?

export function whenMapStateToPropsIsObject(mapStateToProps) {
  if (mapStateToProps && typeof mapStateToProps === 'object') {
    return bindSelectors(mapStateToProps)
  }
}

@timdorr
Copy link
Member

timdorr commented Jan 22, 2019

The issue isn't necessarily hooking it up, it's when it comes to memoizing output and perf issues. That sort of stuff adds a lot more complexity to the code.

@jchook
Copy link

jchook commented Jan 22, 2019

What if we converted it to a function before reaching any of that code?

For example, adding bindSelectors() logic here before anything else.

Pros:

  • Conversion runs once, not on each render
  • Fully supports existing performance enhancements + memoizing

Cons:

  • Might break custom factory stuff?
  • Could require additional option to turn it off?

To illustrate, consider this workaround that shouldn't cause any render performance impact since it simply preps mapStateToProps before giving it to connect():

import { connect as realConnect } from 'react-redux'

export const connect = (Component) => (mapStateToProps, ...args) => (
  realConnect(Component)(
    mapStateToProps && typeof mapStateToProps === 'object'
      ? bindSelectors(mapStateToProps)
      : mapStateToProps,
    ...args
  )
)

@markerikson
Copy link
Contributor

Part of me still kinda wants do to this, but I'll go along with Tim and say it's not worth it to build it in.

@jchook
Copy link

jchook commented Feb 5, 2019

Yeah on second look I agree.

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

5 participants