Closed planetcohen closed 8 years ago
What if I need both users and articles form a larger store, i.e 2 or more scopes, yet not the whole thing? This may well be the case with some components, IMHO
Perhaps articles and users is not the best example; think articles and products instead. I think it's most likely that you'll have views that display articles and separate views that display products.
This particular pattern wouldn't be very useful in my applications. I have higher level connected components that grab all the state that is needed to display a portion of a page that then hand 'scoped' data to dumb components to actually do the rendering. Pages need user state (preferences), ui state, interconnected entity state, etc in order to render anything.
I use reselect
in order to hide the actual shape of the state store from the connected components. My components have no idea what the store actually looks like at all. For example, I have a set of selectors that handle session state.
import { createSelector } from 'reselect';
const sessionSelector = state => state.get('session');
export const currentSessionSelector = createSelector(
sessionSelector,
(session) => session
);
Then my components can get the current session state like this:
function selectorsToProps(state) {
return {
session: sessionSelectors.currentSessionSelector(state),
};
}
export default connect(selectorsToProps)(LoginPage);
Here the component has no idea where the session state is stored, it just knows how to get it. Now I can move it around whenever I want, fix up one selector (the const sessionSelector
) and everything else just works. The other nice thing about reselect
is that it memoizes calls so that you can have logic in the selectors that is also hidden from the components (such as filters/sorting) without slowing things down.
Selectors is our approach to this. Write selectors alongside your reducers, and you never need to know the exact state path. You don't have to use reselect (although it's good for perf). Just group selectors with reducers and require them up the reducer tree so components talk to root selectors. Example
One of the really nice results of reducer composition is that it is possible to create reducers that only need to know about their part of the global state tree. This is unfortunately not currently the case with mapStateToProps, which is handed the complete global state tree. A consequence of this is that when an app gets larger, and you re-organize the state tree, you need to touch every instance of mapStateToProps.
For example, suppose that I start out with an app that deals with articles. I have an ArticlesListView that I connect() to the state like so:
Now, my app grows and I want to introduce functionality related to users. No problem, I simply update my global state like so:
Because of how reducer composition works, I don't need to touch my reduceArticles implementation. But I do need to update my mapStateToProps, since the state it operates on has been moved to the articles sub-key of the global state. That's what I mean when I stated that mapStateToProps is not orthogonal to reducers.
There is a work around with the current implementation of connect, which is to use the store prop on the connected component, like so:
Now, my mapStateToProps function for articles is handed only the articles branch of the global state tree and doesn't need to be updated.
You can make this generic, for any attribute of the global state like so:
This is all possible with the current implementation of connect, and it's what I'm doing in my apps. But it's not obvious and it requires a bunch of plumbing on my part.
Since I believe this will be such a common pattern, I recommend that we make this plumbing easy by adding a scope prop to the connected component like so:
Finally, as an app gets larger, it is likely that state will get further nested. This could be supported by allowing dot-notation on the scope selector like so:
The corresponding scopedStore function would now look like this:
If you guys think this makes sense, I'd be happy to contribute a PR for this.