Closed gaearon closed 9 years ago
For example, we could drop <Connector>
altogether and encourage people to use @connect
decorator as a function in another module.
A dumb component would look exactly as it does now. A smart component would look like
import React from 'react';
import { connect } from 'react-redux';
import Counter from '../components/Counter';
import * as CounterActions from '../actions/CounterActions';
export default connect(
state => ({
counter: state.counter
}),
dispatch => ({
increment: dispatch(CounterActions.increment()),
decrement: dispatch(CounterActions.decrement())
})
)(Counter);
Note that the smart component doesn't have to be declared as a component. Also note that state => ...
and dispatch => ...
is all it accepts.
Want more customization? Want a componentDidUpdate
hook? Want to select different things depending on the current props? Well, maybe you need to put a component in the middle then:
import React from 'react';
import connect from './connect';
import { bindActionCreators } from 'redux';
import Counter from '../components/Counter';
import * as CounterActions from '../actions/CounterActions';
class CounterContainer {
componentDidUpdate() {
...
}
render() {
const props = somehowSelectChildProps(this.props);
return <Counter {...props} />
}
}
export default connect(
state => ({
counter: state.counter
}),
dispatch => ({
increment: () => dispatch(CounterActions.increment()),
decrement: () => dispatch(CounterActions.decrement())
})
)(CounterContainer);
This is an “explicit” smart component that is required for more advanced cases. Note that you didn't have to move files or refactor anything. You just put a component in the middle into the same file.
Finally, we can still offer bindActionCreators
, but with a actionCreators => dispatch => obj
signature, so that the result is usable as the second parameter:
import React from 'react';
import { connect } from 'react-redux';
import Counter from '../components/Counter';
import * as CounterActions from '../actions/CounterActions';
export default connect(
state => ({ counter: state.counter }),
bindActionCreators(CounterActions)
)(Counter);
Perhaps we can even go further and bind automatically if an object is passed.
import React from 'react';
import { connect } from 'react-redux';
import Counter from '../components/Counter';
import * as CounterActions from '../actions/CounterActions';
export default connect(
state => ({ counter: state.counter }),
CounterActions
)(Counter);
“Wait!”, I hear you say. What if an action depends on some prop from the state? Well, in this case you put a component in the middle like I described above.
import React from 'react';
import { connect } from 'react-redux';
import Counter from '../components/Counter';
import * as CounterActions from '../actions/CounterActions';
class CounterContainer {
increment() {
this.props.increment(this.props.id);
}
render() {
return <Counter {...this.props} increment={this.increment} />
}
}
export default connect(
state => ({ counter: state.counter }),
CounterActions
)(CounterContainer);
Any sufficiently complicated case => component in the middle. Easy!
Am I missing something?
:+1: I like this. It's essentially the same API as https://github.com/acdlite/redux-rx#createconnectorselectstate-render, just without streams.
Ah, only thing I see missing is a way to access props from owner — e.g. if you're wrapped by Relay.
Can you clarify? Isn't this “Case with more control” above?
That works, I'm just not sure I like it... Seems like too common a case to necessitate what is essentially two smart components.
Could we just pass the owner props as the second argument?
From the user point of view they're just declaring one “real” component so no big deal IMO. On the other hand once you start selecting data in a tricky way, you begin to want finer control over perf optimizations, lifecycle hooks and maybe passing data down in a trickier way so it's likely you'll want a component anyway.
Once we start passing props to the state getter, we'll also probably want to pass props to the action creators getter. However, this forces us to bind on every prop change, which is a perf hit and unfriendly to shouldComponentUpdate
optimizations down the rendering chain.
Example: you might want to read from props if you have a route handler that gets specific data related to the current route parameters. But then you already want to fire an action in componentWillReceiveProps
and componentDidMount
to fetch the data! So you already need an intermediate component anyway.
So are you just removing the idea of a higher order component here? The signature of "connect" seems the same as what was proposed in #86 (at least the shorthand one you suggested where you just pass an object of actions as the second param).
@gaearon
On the other hand once you start selecting data in a tricky way, you begin to want finer control over perf optimizations, lifecycle hooks and maybe passing data down in a trickier way so it's likely you'll want a component anyway.
I really think accessing props from the owner is a much more common case that using lifecycle hooks. Props passing is the fundamental contract of React. We'll soon live in a world where pure functions are valid React components. The fewer "smart" components the better — creating class components will start to become a low-level implementation detai. Function components will be the new default. (At least that's what should happen. We'll see if the community catches on.)
However, this forces us to bind on every prop change, which is a perf hit and unfriendly to shouldComponentUpdate optimizations down the rendering chain.
This seems like a micro-optimization but okay. You could get around this by binding once if an object is passed ("bind automatically if an object is passed") but bind every time if its a function. Also if an action creator depends on a prop it's going to lead to updates further down the rendering chain, anyway.
^ The reason I say it's a micro-optimization is you're rarely going to pass action creators more than one level down the component tree.
@acdlite also, it's easy enough to prevent constant rebinding with memoization. We had explored this a bit in redux#86
I also agree with the sentiment that you'll rarely pass action creators more than one level down a component tree. This makes me question the over all usefulness of bindActionCreators
. It seems to contrast the simplicity and obviousness of redux in general. It seems like it'd be clearer to just force users to pass dispatch
down as a prop to every component, as described in the current readme file.
As a user, it seems like if dispatch
is always needed to call an action, then maybe some way to remove the onus on the developer to pass dispatch
down the tree. It'd be cool if you could just import your action methods and call them, no?
@ryancole I still think bindActionCreators()
(or some equivalent) is useful. You shouldn't pass dispatch()
to a dumb component; you should bind it in a smart component first. E.g. your dumb components should look like this:
class CounterButton extends Component {
render() {
const { increment } = this.props;
<button onClick={increment} />;
}
}
Rather than this:
class CounterButton extends Component {
render() {
const { dispatch } = this.props;
<button onClick={dispatch(increment())} />;
}
}
It may seem like a trivial difference, but the first version is more separated from the implementation details of how it receives its callback. This leads to more scalable, maintainable, testable code.
It'd be cool if you could just import your action methods and call them, no?
You have to bind them somewhere. Remember, action creators in Redux are simply pure functions. Either we bind them in smart components, or we have to bind them at store creation time, in which case we'd need some sort of API for accessing the bound components. Or you could use singletons, but yuck.
I like @gaearon's idea of passing action creators as the second param and auto-binding:
export default connect(
state => ({ counter: state.counter }),
CounterActions
)(CounterContainer);
That way we only need to bind once, and the user doesn't need to worry about bindActionCreators.
I would amend that proposal to also support a second form, where you pass a function that maps to unbound action creators:
export default connect(
state => ({ counter: state.counter }),
(state, props) => ({
increment: () => CounterActions.increment(props.something)
})
)(CounterContainer);
that way you can access the store state and props, if needed. bindActionCreators()
becomes an implementation detail.
@acdlite I agree with how you explained why bindActionCreators
is needed, now. I wasn't thinking in terms of smart and dumb components.
Although something about the idea of a tree of components having most of the parent, outer-most components as smart components and then all the edge node components as dumb (this is what I think this smart / dumb component pattern lends itself to) kind of seems like a stink. I don't have an optimal pattern in mind, and I know smart / dumb components are a current popular pattern, but this as a pattern seems like it creates scenarios where if a dumb component is way down the tree you'll have to pass down action methods or dispatch all the way down the tree to get to it, thus making the components on the way to that component possibly carry along unneeded props just to satisfy their children. Maybe this is result of bad component tree design or something, though, on my part.
Just thinking outside the box here, but what if bound actions were just a part of the state:
export default connect(
state => ({ counter: state.counter,
increment: state.actions.counter.increment(state.something) })
)(CounterContainer);
or, less radical:
export default connect(
(state, bind) => ({ counter: state.counter,
increment: bind(CounterActions.increment(state.something)) })
)(CounterContainer);
It seems weird to me to do generate two separate objects that are ultimately merged into the child's props.
@aaronjensen Regarding your first proposal:
First of all, the state object is not necessarily a plain JavaScript object. Redux makes no assumptions about the type of state returned from the reducer. For instance, you could use an Immutable Map.
Second of all, where are the action creators coming from? You say they're part of the state, but how did they get there? We'd need some sort of system for registering action creators at the global level.
@acdlite Yep, that's right. We have a system for registering reducers, it didn't seem a long stretch to have one for actions. And you're right re: state not being a plain JS object of course. In that case two arguments could come along: (state, actions)
or actions could have their own reducer but that seems a little odd.
Tbh, I can't think of a particularly compelling reason for it other than slight convenience at that point at the cost of required registration.
How is globally registering action creators more elegant than this?
export default connect(
state => ({ counter: state.counter }),
CounterActions
)(CounterContainer);
EDIT: nevermind, I see that you changed your mind :)
Yeah, that solution does look good. It didn't sit well with me at first that props were being combined in secret, but being able to handle the binding automatically makes it worth it. I also like your proposal for the second form which takes a function.
It'd probably be a bad idea to assume that all functions are action creators, yea?
export default connect(
(state, props) => ({
counter: state.counter,
increment: () => CounterActions.increment(props.something)
})
)(CounterContainer);
If not, then it allows for everything w/ one argument:
export default connect(
state => ({
counter: state.counter
...CounterActions,
...OtherActions,
}),
)(CounterContainer);
You could also make it an n-arity function and just merge all of the objects (binding all functions automatically).
This only works if it's safe to assume all functions are action creators though...
we could drop
<Connector>
altogether and encourage people to use@connect
decorator
I think it's a good idea, and we can do the same for Provider
. This will simplify the API and create less confusion.
This leads to more scalable, maintainable, testable code
@acdlite I was also thinking of dropping bindActionCreators
, as it's just syntactic sugar for dispatch(myAction())
, but you make a valid point.
And passing the actions as a second argument of connect
makes it a good API, given that the binding becomes an implementation detail of the decorator and the user doesn't care about it.
One thing I would also like to have is namespacing props. Basically instead of just spreading the actions or whatever to this.props
, we can have a actions
object that contains all the actions, and just pass the object to props
. Same thing could be done for state
. I think this is important when you start having other data in props
(e.g.: router) and helps avoiding possible conflicts when merging props.
Here an example:
// Instead of a flat structure like
this.props = {
children: Object,
dispatch: Function,
// state
todos: Object,
counter: Object,
// actions
addTodo: Object,
increment: Object,
decrement: Object,
// router
isTransitioning: Boolean,
location: Object,
params: Object,
route: Object,
routeParams: Object,
// custom props
foo: Object,
onClick: Function
}
// we could have a much cleaner structure
this.props = {
children: Object,
dispatch: Function,
// state
state: {
todos: Object,
counter: Object
},
// actions
actions: {
addTodo: Object,
increment: Object,
decrement: Object
},
// router
router: {
isTransitioning: Boolean,
location: Object,
params: Object,
route: Object,
routeParams: Object
},
// custom props
foo: Object,
onClick: Function
}
Thoughts?
I really think accessing props from the owner is a much more common case that using lifecycle hooks.
Generally connecting to Redux should be done at route handler level, and in this case you need the hooks too. Smart components close to the top level rarely receive props that somehow uniquely identify them, so even if they have props, I doubt these props are so relevant to selecting the state. For example, you won't connect <TodoItem>
—you'll probably connect the whole <TodoList>
in which case props are irrelevant, as you want to select the whole related slice of state.
Can you please help me by providing a few examples where props are important at the connect level?
This seems like a micro-optimization
It's really not. :-) It seems like a micro-optimization but it's the beginning of death by a thousand cuts. Redux connector sits close to the top of the application, and if at this level we're getting new functions on every prop change, no component down the chain can benefit from shouldComponentUpdate
. The whole subtree is contaminated by a few changing functions.
Surely you won't see this problem at the beginning, but as soon as you get a perf bottleneck in one of your components, adding PureRenderMixin
to it won't “just work” anymore because Redux rebinds these actions on every prop change. We don't want people to end up in this situation.
You could get around this by binding once if an object is passed ("bind automatically if an object is passed") but bind every time if its a function.
I can.. But then changing two seemingly equivalent signatures will have bad perf consequences for the whole rendering chain. It's too easy to make this mistake and later have no idea how to optimize your app because shouldComponentUpdate
stopped helping anywhere down the chain.
On the other hand, if we force user to create a component, this won't be a problem, as they will pass the component's functions down. And the component's functions can look into props just fine.
Also if an action creator depends on a prop it's going to lead to updates further down the rendering chain, anyway.
Yes, but in a way totally manageable by shouldComponentUpdate
! If unrelatedToHeavyComponents
state changes too often, but <HeavyComponents>
down the chain accept increment
, they won't update every time unrelatedToHeavyComponents
changes. On the other hand, if we go with binding on prop change, <HeavyComponents>
will receive new increment
every time unrelatedToHeavyComponents
changes and they'll have to re-render. We don't know for sure which props are used by which action creators. I still think never binding is the easiest solution. It's not too hard to write a component, and you're in full control if you do.
^ The reason I say it's a micro-optimization is you're rarely going to pass action creators more than one level down the component tree.
Can you elaborate on that? I usually pass them several levels down (at some point they turn into dumb on*
props but I still pass them down).
it's easy enough to prevent constant rebinding with memoization
Memoization only avoids “rebind on every render” in favor of “rebind on every prop change”. It's certainly better than nothing, but worse than “never rebind”.
@emmenko Namespacing will also kill shouldComponentUpdate
optimizations because you can't shallowly compare props anymore and nobody will write code to handle namespaces separately.
@gaearon right, haven't consider this. I guess there's no much way around it then...
How about we add a third parameter: merge
.
Default:
export default connect(
state => ({ counter: state.counter }),
CounterActions,
(state, actions, props) => ({
...state,
...actions
})
)(Counter);
But you can also...
export default connect(
state => ({ counter: state.counter }),
CounterActions,
(state, actions, props) => ({
...state,
...actions,
increment: (...args) => actions.increment(props.counterId, ...args)
})
)(Counter);
This gives you the same control, but any binding is explicit and performed by you.
In fact as long as you don't care for lifecycle hooks you can do this:
export default connect(
state => ({ counters: state.counters }),
CounterActions,
(state, actions, props) => ({
counter: state.counters[props.counterId],
increment: () => actions.increment(props.counterId)
})
)(Counter);
Binding is performed by you instead of the library, so you can easily find where it happens if you have shouldComponentUpdate
problems. If this begins to work bad performance-wise, you have a clear upgrade path: turn merge
function into a component.
So to be clear, connect
would have following signature now?
function connect (state: Function, actions: Object, merge: Function)
This gives you the same control, but any binding is explicit and performed by you.
Not sure exactly what do you mean by binding
though. This has nothing to do with "binding the dispatch", right?
you have a clear upgrade path: turn
merge
function into a component
You mean by putting a component in the middle like in your first example? Or do you mean something else?
Thanks!
You said above that "connecting" components using usually happens at or near the route handler level.
I wholeheartedly agree with this, and is definitely what I've experienced in general. When you bind to Redux (or really, binding to any other Flux library) too far down, then changes are difficult to trace and weird things start to happen.
However, that doesn't mean that you wouldn't want to provide the action creators deeper in the tree. That leads me to believe that maybe we can keep "connect" simple - don't worry about binding any action creators with it - and then introduce some other helper that can be a convienence to bind action creators on a component level.
I worry that connect is becoming too heavy, and originally I really liked how simple connect was to understand. It's almost self-documenting.
An example: if I have a
The only thing I really want to get around here is just that explicit passing. I think the dispatching is an implementation detail (albeit an important one). As the user of Redux, I just want to be able to call my action creator, not pass dispatch three layers deep explicitly through props.
So to be clear, connect would have following signature now?
function connect (state: Function, actions: Object, merge: Function)
To be honest I'd still rather go with:
connect(
State => StateProps,
?(Dispatch => DispatchProps),
?(StateProps, DispatchProps, ParentProps => Props)
)
The reason I don't want “magic” behavior for objects is people will see this once and use everywhere. It won't be obvious that you don't have to pass an object.
In most cases I'll do this:
connect(
select,
bindActionCreators(CounterActions)
)
But there are cases where another option is nicer, for example:
connect(
select,
dispatch => ({ dispatch })
)(MySmartComponent) // I want to use vanilla this.props.dispatch
See, I might not want binding a particular set of action creators at all!
Or maybe parameterized actions:
connect(
select,
dispatch => dispatch,
(state, dispatch, props) => ({
...state,
...bindActionCreators(createMyActions(props.apiUrl))
})
)(MySmartComponent)
(Again, this is not super performant but my conscience is clear because binding occurs in user code.)
So I'd rather not have the shortcut, and instead force user to understand how bindActionCreator
works as a utility. This goes in line with some other shortcuts we're removing in Redux 1.0 API in favor of explicit equivalents.
Not sure exactly what do you mean by binding though. This has nothing to do with "binding the dispatch", right?
Why, that's exactly what I mean :-). In my example, increment: (...args) => actions.increment(props.counterId, ...args)
is akin to function binding, but it's performed in your code instead of library code, so it's easier to find this as a perf regression culprit.
You mean by putting a component in the middle like in your first example? Or do you mean something else?
Yes. As soon as you have perf problems you just “upgrade” (or maybe “downgrade” :-) your function to a proper component. It's similar to how you'd probably “upgrade” pure functions to components after React 0.14 if there are some perf optimizations you can't do in a pure function for whatever reason.
@gaearon @acdlite considering another approach, what would be the pros / cons of using context
?
Currently we have getState
and dispatch
there. Would it make sense to have also getActions
? Then you don't have to pass them around anymore via props
, you can just get them from the context
. This would also keep connect
a simple component that subscribes to changes.
I don't know, just thinking out loud...
@emmenko this is kind of what I'm getting at as well, but admittedly have not though through possible cons of the "context" idea.
However, that doesn't mean that you wouldn't want to provide the action creators deeper in the tree.
I think binding action creators at different tree levels is an anti-pattern. You should strive to do this at the top. Passing additional props as callbacks is really what React is all about.
You can always get around it by having connect(() => ({}), bindActionCreators(MyActions))
in the middle of the tree but it looks weird and I think that's a good thing, as it will encourage people to follow React's explicit way.
I think the dispatching is an implementation detail (albeit an important one). As the user of Redux, I just want to be able to call my action creator, not pass dispatch three layers deep explicitly through props.
That leads me to believe that maybe we can keep "connect" simple - don't worry about binding any action creators with it - and then introduce some other helper that can be a convienence to bind action creators on a component level.
I used to think about connect
as a way of subscribing to changes, but I don't anymore. I think connect
should be the boundary between Redux-aware and Redux-unaware parts of your component tree. Therefore it fits it to be slightly more powerful, so that components below are completely untied of Redux. Now I think that wherever you materialize the state, you should materialize the actions too.
Would it make sense to have also getActions?
Action creators are not a core concept of Redux. There is no reason for them to live at the top. They are just convenience. Sometimes you want a factory of action creators that depends on some props. Can't do this at the very top.
After all if you really want those action creators to be in context, you can do this yourself: https://gist.github.com/mattapperson/db45538f14d6de52f6ad
But it's on your conscience. :-)
I'd love to hear some thoughts from @faassen here.
Also at this point wrapActionCreators
might be a better naming than bindActionCreators
.
After all if you really want those action creators to be in context, you can do this yourself
Sure, I'm just always a bit afraid how much is it ok to use context
. But just to understand, if actions were a core concept, would it have been ok to put them into context
and just access them wherever I want?
Anyway, are we heading now towards this connect
signature and give it more responsibilities, as you said?
connect(
State => StateProps,
?(Dispatch => DispatchProps),
?(StateProps, DispatchProps, ParentProps => Props)
)
But just to understand, if actions were a core concept, would it have been ok to put them into context and just access them wherever I want?
Can you rephrase? I don't understand what exactly you are asking.
Anyway, are we heading now towards this connect signature and give it more responsibilities, as you said?
This signature is what feels right to me at the moment. Waiting for @acdlite to take a look.
In fact this is more correct signature:
connect(
State => StateAny,
?(Dispatch => DispatchAny),
?(StateAny, DispatchAny, ParentProps => Props)
)
We don't really care if they are objects until the third function is applied.
For example, you can do:
connect(
state => state.counter,
wrapActionCreators(CounterActions),
(counter, actions) => ({ counter, ...actions })
)
or:
connect(
state => state.counter,
dispatch => dispatch,
(counter, dispatch) => ({ counter, dispatch })
)
but also:
connect(
state => ({ counter: state.counter }),
wrapActionCreators(CounterActions)
)
as before.
Just wanted to know how much one is it ok to use context and if it would have been a good approach, in case actions were considered an important part of redux. Just for my understanding of designing a good API :)
Hope it's clear now, thanks!
Named arguments:
connect({
state: state => ({ state.counter }),
actions: dispatch => ({
increment: dispatch(CounterActions.increment())
})
})(Counter)
connect({
state: state => ({ state.counter }),
actions: wrapActionCreators(CounterActions)
})(Counter)
connect({
state: state => ({ state.counter })
})(Counter)
connect({
actions: wrapActionCreators(CounterActions)
})(Counter)
connect({
actions: dispatch => ({ dispatch })
})(Counter)
function wrapActionCreatorMap(map) {
return dispatch => mapValues(map, actionCreators => wrapActionCreators(actionCreators)(dispatch));
}
connect({
state: state => state.counter,
actions: wrapActionCreatorMap({
counterActions: CounterActions,
userActions: UserActions
})
})(Counter)
connect({
state: state => state.counter,
actions: wrapActionCreators(...CounterActions, ...UserActions)
})(Counter)
function merge(counter, actions, props) {
return { counter, actions, ...props };
}
connect({
state: state => state.counter,
actions: wrapActionCreators(CounterActions)
}, merge)(Counter)
function merge(counters, actions, props) {
const { counterId, ...rest } = props;
const { increment, decrement } = actions;
return {
counter: counters[counterId],
increment: () => increment(counterId),
decrement: () => decrement(counterId),
...rest
};
}
connect({
state: state => state.counters,
actions: wrapActionCreators(CounterActions)
}, merge)(Counter)
Normal arguments:
connect(
state => ({ state.counter }),
dispatch => ({
increment: () => dispatch(CounterActions.increment())
})
)(Counter)
connect({
state => ({ state.counter }),
wrapActionCreators(CounterActions)
})(Counter)
connect(
state => ({ state.counter })
)(Counter)
connect(
null, // means "no need to subscribe at all"
wrapActionCreators(CounterActions)
)(Counter)
connect(
null,
dispatch => ({ dispatch })
)(Counter)
function wrapActionCreatorMap(map) {
return dispatch => mapValues(map, actionCreators => wrapActionCreators(actionCreators)(dispatch));
}
connect(
state => state.counter,
wrapActionCreatorMap({
counterActions: CounterActions,
userActions: UserActions
})
)(Counter)
connect(
state => state.counter,
wrapActionCreators(...CounterActions, ...UserActions)
)(Counter)
function merge(counter, actions, props) {
return { counter, actions, ...props };
}
connect(
state => state.counter,
wrapActionCreators(CounterActions),
merge
)(Counter)
function merge(counters, actions, props) {
const { counterId, ...rest } = props;
const { increment, decrement } = actions;
return {
counter: counters[counterId],
increment: () => increment(counterId),
decrement: () => decrement(counterId),
...rest
};
}
connect(
state => state.counters,
wrapActionCreators(CounterActions),
merge
)(Counter)
I like this! :+1:
I mean the named args (sorry, didn't see you posted the other approach)
I prefer the second version because:
actions
(because they are actionCreators
but that's tedious to type)merge
argument looks more naturally as the third argumentnull
is like an explicit cry for “don't subscribe!”:+1: I prefer the second version, too. It addresses my primary concern from above, which is to access to owner props without needing an extra smart component in the middle, without sacrificing default performance optimizations.
@acdlite
If we agree on that, what do you think is reasonable release-wise? We could release this version as react-redux@1.0.0
, or we could release the current version as 1.0 but jump to 2.0 with this proposal.
It probably makes sense to use the new version in new docs..
@gaearon do you think connect
is still a correct name for the new signature?
@gaearon I think we should hold of moving react-redux to 1.0 for a while until we've settled on the new API. The current API is not a 1.0 in my view, and I don't see any problem in keeping react-redux at 0.12 (or whatever) even while Redux gets a 1.0 release. That's the advantage of separating the projects.
Random musings...
<Provider>
is a bit generic, would <Redux>
work? For somebody new to the framework, it helps them immediately get oriented.
I like 'connect', as in 'connect to Redux, the state manager.' Just brainstorming, but 'inject' might also work... inject (state and wrapped action creators).
Regarding:
connect({
state: state => ({ state.counter }),
actions: wrapActionCreators(CounterActions)
})(Counter)
I know it's longer, but I think it would be better to use actionCreators: wrapActionCreators(...)
. First, this is already the less-preferred, more typerific version of the proposed API. Second, elsewhere we are careful to differentiate between actions and action creators, so I think the value in maintaining the distinction in the API is worth the extra typing for those who want to use this more explicit syntax.
Also, in this version of the API, could we not just pass an array of actionCreators?
connect({
state: state => ({ state.counter }),
actionCreators: CounterActions
})(Counter)
Regarding the <Connector>
syntax, select=
feels a little off for some reason. Hmmm, maybe it's an echo from using Ruby years ago, but select
makes me feel like the function is filtering an enumeration, rather than just directly grabbing a slice of the passed in state parameter. Perhaps slice
or pick
or something along those lines. Then again we're passing in a function, so maybe the term should make that clear, so more like slicer=
or picker=
? Eg. the slicer function returns the slice of state we want access to.
Just out of curiosity, why was wrapActionCreators better than bindActionCreators?
This API is taken from Redux
<Provider>
,<Connector>
,@provide
,@connect
. Right now I don't have examples in this repo but it matches what you've seen in Redux so far.It's the best we've got now, but I think we can do better!
Common pain points:
<Connector>
,@connect
bindActionCreators
helper which some don't like<Provider>
,<Connector>
both need function children)Let's go wild here. Post your alternative API suggestions.
They should satisfy the following criteria:
store
instance. (Akin to<Provider>
)select
functioncomponentDidUpdate
select
function needs to be able to take their props into accountshouldComponentUpdate
wherever we can