How to write Redux selectors that are both reusable and modular? - javascript

I am new to Redux and trying to figure out how to take full advantage of it.
When writing a selector for a module of the application, what part of the state tree should be passed to the selector so that the selector is both reusable and modular?
For example, given the code below, what is a good way to write selectModuleItemsById with a state shape similar to stateShapeExample?
let stateShapeExample = {
module: {
items: {
firstItemId: {...},
secondItemId: {...},
...
}
}
}
const selectModuleRoot = (state) => state.module;
// First Option: starts from the module root
const selectModuleItemById = (state, id) => state.items[id];
// Second Option: starts from the global root
const selectModuleItemById = (state, id) => state.module.items[id];
// Something Else: ???
const selectItemById = (state, id) => state[id];

The short answer is that it's pretty tricky.
The best writeup on this that I've seen is Randy Coulman's series of posts on modularizing selectors:
Encapsulating the Redux State Tree
Redux Reducer/Selector Asymmetry
Modular Reducers and Selectors
Solving Circular Dependencies in Modular Redux
The general summary of that seems to be letting "module reducers" write selectors that know how to pick pieces of data out of their own state, then "globalizing" them at the app level based on where that module/slice is mounted in the state tree. However, since the module probably needs to use the selectors itself, you may have to move the registration / setup process into a separate file to avoid a circular dependency issue.

Selectors, by definition, take in the entire state and return a portion of the state. Anything else is basically just a data utility function.
I use ramda lenses to manage this kind of thing.
Consider a directory structure like this:
store
module
data.js
selectors.js
reducers.js
actions.js
data.js would export the initial state (in this case, just the initial state for modules) and ramda lenses that describe where pieces of state are.
import { lensPath } from 'ramda'
export default {
items: {
firstItemId: {...},
secondItemId: {...},
...
}
}
export const itemsLens = lensPath(['module', 'items'])
export const makeItemLens = id => lensPath(['module', 'items', id])
Then, in selectors.js you import the lenses to select the data from the entire state tree.
import {view} from 'ramda'
import {itemsLens, makeItemLens} from './data.js'
export const selectModuleItems = state => view(itemsLens, state)
export const selectModuleItemById = (state, id) => view(makeItemLens(id), state)
This strategy has a few benefits:
Using ramda's lensPath with view enables you to do deep property lookups without risking, Cannot read propery firstItemId of undefined errors. Other libraries have equivalent functions if ramda aint your thing (lodash, immutable.js, etc).
Having the lenses alongside your initial state adds a lot of clarity for other developers.
Abstracting object paths to lenses makes it a bit easier to restructure your state tree, if needed.
The downside is that it's a bunch of extra boilerplate code, but there's value in being explicit and avoiding magical code IMO.
Having said all that, you should also check out reselect for more advanced selector strategies (something I have yet to play with extensively).

Related

Would I lose any benefits if I switch from Vuex to custom Javascript store?

I am making a game editor. All it needs is a way to save and read data from a common store, such as sprites and tool settings.
The problem is Vuex just seems really messy to me. Maybe it's because I'm not building a standard SPA which Vuex was designed for, but it just seems that every time I want to do something simple it adds 50+ lines of code in getters, actions, and mutations that would otherwise be unnecessary. On top of that, it has limitations such as not being able to modify states from getters which would be really helpful when generating unique asset IDs. I also have no need for the dynamic loading/unloading of modules.
So my question, if I replaced Vuex with an imported object like the following:
class MyStore_Class{
constructor(){
this.val = 0;
}
//other methods and stuff to manipulate data
}
let MyStore = new MyStore();
export default MyStore;
Then imported this MyStore object into the components where I needed it, would I lose anything?
I ran some simple tests and it seems like it works perfectly as a drop in replacement for Vuex, but I'm afraid there might be some kind of downside that I would notice only later down the line.
EXIT: Pretty much all data for the app is local, so the separation of actions/mutations tends to mean that the only action code I am writing is commit('doMutation', newData) over and over again
Your solution could be a vue observable, really easy to do and lightweight in term of architecture ;)
Create a store.js file in your src/root folder
Create the state value/s you wish to have globally
Create the methods you needs for his interaction
Set it up in your components and there you go
Setup store.js
import Vue from "vue";
const state = Vue.observable({ val: 0 });
export const increment = () => state.counter++;
export const decrement = () => state.counter--;
export default state;
In your component
<template>
<div>
<p>The value is {{val}}</p>
<button #click="inc">+</button>
<button #click="dec">-</button>
</div>
</template>
<script>
import store, { increment, decrement } from "./store";
export default {
computed: {
// your getter in some way
counter() {
return store.counter;
}
},
methods: {
inc() {
increment();
},
dec() {
decrement();
}
}
};
</script>
I took these examples on this article, where you could read more about vue observable if you want, but i use it a lot on small projects where i need just few values accessible globally and that doesn't require a vuex architecture.
https://medium.com/better-programming/how-to-manage-vues-state-with-vue-observable-25988a88938b

simplify redux with generic action & reducer

In React-Redux project, people usually create multiple actions & reducers for each connected component. However, this creates a lot of code for simple data updates.
Is it a good practice to use a single generic action & reducer to encapsulate all data changes, in order to simplify and fasten app development.
What would be the disadvantages or performance loss using this method. Because I see no significant tradeoff, and it makes development much easier, and we can put all of them in a single file! Example of such architecture:
// Say we're in user.js, User page
// state
var initialState = {};
// generic action --> we only need to write ONE DISPATCHER
function setState(obj){
Store.dispatch({ type: 'SET_USER', data: obj });
}
// generic reducer --> we only need to write ONE ACTION REDUCER
function userReducer = function(state = initialState, action){
switch (action.type) {
case 'SET_USER': return { ...state, ...action.data };
default: return state;
}
};
// define component
var User = React.createClass({
render: function(){
// Here's the magic...
// We can just call the generic setState() to update any data.
// No need to create separate dispatchers and reducers,
// thus greatly simplifying and fasten app development.
return [
<div onClick={() => setState({ someField: 1 })}/>,
<div onClick={() => setState({ someOtherField: 2, randomField: 3 })}/>,
<div onClick={() => setState({ orJustAnything: [1,2,3] })}/>
]
}
});
// register component for data update
function mapStateToProps(state){
return { ...state.user };
}
export default connect(mapStateToProps)(User);
Edit
So the typical Redux architecture suggests creating:
Centralized files with all the actions
Centralized files with all the reducers
Question is, why a 2-step process? Here's another architectural suggestion:
Create 1 set of files containing all the setXField() that handle all the data changes. And other components simply use them to trigger changes. Easy. Example:
/** UserAPI.js
* Containing all methods for User.
* Other components can just call them.
*/
// state
var initialState = {};
// generic action
function setState(obj){
Store.dispatch({ type: 'SET_USER', data: obj });
}
// generic reducer
function userReducer = function(state = initialState, action){
switch (action.type) {
case 'SET_USER': return { ...state, ...action.data };
default: return state;
}
};
// API that we export
let UserAPI = {};
// set user name
UserAPI.setName = function(name){
$.post('/user/name', { name }, function({ ajaxSuccess }){
if (ajaxSuccess) setState({ name });
});
};
// set user picture URL
UserAPI.setPicture = function(url){
$.post('/user/picture', { url }, function({ ajaxSuccess }){
if (ajaxSuccess) setState({ url });
});
};
// logout, clear user
UserAPI.logout = function(){
$.post('/logout', {}, function(){
setState(initialState);
});
};
// Etc, you got the idea...
// Moreover, you can add a bunch of other User related methods,
// like some helper methods unrelated to Redux, or Ajax getters.
// Now you have everything related to User available in a single file!
// It becomes much easier to read through and understand.
// Finally, you can export a single UserAPI object, so other
// components only need to import it once.
export default UserAPI
Please read through the comments in the code section above.
Now instead of having a bunch of actions/dispatchers/reducers. You have 1 file encapsulating everything needed for the User concept. Why is it a bad practice? IMO, it makes programmer's life much easier, and other programmers can just read through the file from top to bottom to understand the business logic, they don't need to switch back and forth between action/reducer files. Heck, even redux-thunk isn't needed! And you can even test the functions one by one as well. So testability is not lost.
Firstly, instead of calling store.dispatch in your action creator, it should return an object (action) instead, which simplifies testing and enables server rendering.
const setState = (obj) => ({
type: 'SET_USER',
data: obj
})
onClick={() => this.props.setState(...)}
// bind the action creator to the dispatcher
connect(mapStateToProps, { setState })(User)
You should also use ES6 class instead of React.createClass.
Back to the topic, a more specialised action creator would be something like:
const setSomeField = value => ({
type: 'SET_SOME_FIELD',
value,
});
...
case 'SET_SOME_FIELD':
return { ...state, someField: action.value };
Advantages of this approach over your generic one
1. Higher reusability
If someField is set in multiple places, it's cleaner to call setSomeField(someValue) than setState({ someField: someValue })}.
2. Higher testability
You can easily test setSomeField to make sure it's correctly altering only the related state.
With the generic setState, you could test for setState({ someField: someValue })} too, but there's no direct guarantee that all your code will call it correctly.
Eg. someone in your team might make a typo and call setState({ someFeild: someValue })} instead.
Conclusion
The disadvantages are not exactly significant, so it's perfectly fine to use the generic action creator to reduce the number of specialised action creators if you believe it's worth the trade-off for your project.
EDIT
Regarding your suggestion to put reducers and actions in the same file: generally it's preferred to keep them in separate files for modularity; this is a general principle that is not unique to React.
You can however put related reducer and action files in the same folder, which might be better/worse depending on your project requirements. See this and this for some background.
You would also need to export userReducer for your root reducer, unless you are using multiple stores which is generally not recommended.
I mostly use redux to cache API responses mostly, here are few cases where i thought it is limited.
1) What if i'm calling different API's which has the same KEY but goes to a different Object?
2) How can I take care if the data is a stream from a socket ? Do i need to iterate the object to get the type(as the type will be in the header and response in the payload) or ask my backend resource to send it with a certain schema.
3) This also fails for api's if we are using some third party vendor where we have no control of the output we get.
It's always good to have control on what data going where.In apps which are very big something like a network monitoring application we might end up overwriting the data if we have same KEY and JavaScript being loosed typed may end this to a lot weird way this only works for few cases where we have complete control on the data which is very few some thing like this application.
Okay i'm just gonna write my own answer:
when using redux ask yourself these two questions:
Do I need access to the data across multiple components?
Are those components on a different node tree? What I mean is it isn't a child component.
If your answer is yes then use redux for these data as you can easily pass those data to your components via connect() API which in term makes them containers.
At times if you find yourself the need to pass data to a parent component, then you need to reconsider where your state lives. There is a thing called Lifting the State Up.
If your data only matters to your component, then you should only use setState to keep your scope tight. Example:
class MyComponent extends Component {
constructor() {
super()
this.state={ name: 'anonymous' }
}
render() {
const { name } = this.state
return (<div>
My name is { name }.
<button onClick={()=>this.setState({ name: 'John Doe' })}>show name</button>
</div>)
}
}
Also remember to maintain unidirectional data flow of data. Don't just connect a component to redux store if in the first place the data is already accessible by its parent component like this:
<ChildComponent yourdata={yourdata} />
If you need to change a parent's state from a child just pass the context of a function to the logic of your child component. Example:
In parent component
updateName(name) {
this.setState({ name })
}
render() {
return(<div><ChildComponent onChange={::this.updateName} /></div>)
}
In child component
<button onClick={()=>this.props.onChange('John Doe')}
Here is a good article about this.
Just practice and everything will start to make sense once you know how to properly abstract your app to separate concerns. On these matter composition vs ihhertitance and thinking in react are a very good read.
I started writing a package to make it easier and more generic. Also to improve performance. It's still in its early stages (38% coverage). Here's a little snippet (if you can use new ES6 features) however there is also alternatives.
import { create_store } from 'redux';
import { create_reducer, redup } from 'redux-decorator';
class State {
#redup("Todos", "AddTodo", [])
addTodo(state, action) {
return [...state, { id: 2 }];
}
#redup("Todos", "RemoveTodo", [])
removeTodo(state, action) {
console.log("running remove todo");
const copy = [...state];
copy.splice(action.index, 1);
return copy;
}
}
const store = createStore(create_reducer(new State()));
You can also even nest your state:
class Note{
#redup("Notes","AddNote",[])
addNote(state,action){
//Code to add a note
}
}
class State{
aConstant = 1
#redup("Todos","AddTodo",[])
addTodo(state,action){
//Code to add a todo
}
note = new Note();
}
// create store...
//Adds a note
store.dispatch({
type:'AddNote'
})
//Log notes
console.log(store.getState().note.Notes)
Lots of documentation available on NPM. As always, feel free to contribute!
A key decision to be made when designing React/Redux programs is where to put business logic (it has to go somewhere!).
It could go in the React components, in the action creators, in the reducers, or a combination of those. Whether the generic action/reducer combination is sensible depends on where the business logic goes.
If the React components do the majority of the business logic, then the action creators and reducers can be very lightweight, and could be put into a single file as you suggest, without any problems, except making the React components more complex.
The reason that most React/Redux projects seem to have a lot of files for action creators and reducers because some of the business logic is put in there, and so would result in a very bloated file, if the generic method was used.
Personally, I prefer to have very simple reducers and simple components, and have a large number of actions to abstract away complexity like requesting data from a web service into the action creators, but the "right" way depends on the project at hand.
A quick note: As mentioned in https://stackoverflow.com/a/50646935, the object should be returned from setState. This is because some asynchronous processing may need to happen before store.dispatch is called.
An example of reducing boilerplate is below. Here, a generic reducer is used, which reduces code needed, but is only possible the logic is handled elsewhere so that actions are made as simple as possible.
import ActionType from "../actionsEnum.jsx";
const reducer = (state = {
// Initial state ...
}, action) => {
var actionsAllowed = Object.keys(ActionType).map(key => {
return ActionType[key];
});
if (actionsAllowed.includes(action.type) && action.type !== ActionType.NOP) {
return makeNewState(state, action.state);
} else {
return state;
}
}
const makeNewState = (oldState, partialState) => {
var newState = Object.assign({}, oldState);
const values = Object.values(partialState);
Object.keys(partialState).forEach((key, ind) => {
newState[key] = values[ind];
});
return newState;
};
export default reducer;
tldr It is a design decision to be made early on in development because it affects how a large portion of the program is structured.
Performance wise not much. But from a design perspective quite a few. By having multiple reducers you can have separation of concerns - each module only concerned with themselves. By having action creators you add a layer of indirection -allowing you to make changes more easily. In the end it still depends, if you don't need these features a generic solution helps reduce code.
First of all, some terminology:
action: a message that we want to dispatch to all reducers. It can be anything. Usually it's a simple Javascript object like const someAction = {type: 'SOME_ACTION', payload: [1, 2, 3]}
action type: a constant used by the action creators to build an action, and by the reducers to understand which action they have just received. You use them to avoid typing 'SOME_ACTION' both in the action creators and in the reducers. You define an action type like const SOME_ACTION = 'SOME_ACTION' so you can import it in the action creators and in the reducers.
action creator: a function that creates an action and dispatches it to the reducers.
reducer: a function that receives all actions dispatched to the store, and it's responsible for updating the state for that redux store (you might have multiple stores if your application is complex).
Now, to the question.
I think that a generic action creator is not a great idea.
Your application might need to use the following action creators:
fetchData()
fetchUser(id)
fetchCity(lat, lon)
Implementing the logic of dealing with a different number of arguments in a single action creator doesn't sound right to me.
I think it's much better to have many small functions because they have different responsibilities. For instance, fetchUser should not have anything to do with fetchCity.
I start out by creating a module for all of my action types and action creators. If my application grows, I might separate the action creators into different modules (e.g. actions/user.js, actions/cities.js), but I think that having separate module/s for action types is a bit overkill.
As for the reducers, I think that a single reducer is a viable option if you don't have to deal with too many actions.
A reducer receives all the actions dispatched by the action creators. Then, by looking at the action.type, it creates a new state of the store. Since you have to deal with all the incoming actions anyway, I find it nice to have all the logic in one place. This of course starts to be difficult if your application grows (e.g. a switch/case to handle 20 different actions is not very maintainable).
You can start with a single reducer, the move to several reducers and combine them in a root reducer with the combineReducer function.

Why does React-boilerplate selector.js export methods which call createSelector instead of exporting selector directly?

I'm using react-boilerplate which in turn uses reselect. I've noticed their use of reselect is a bit different then how reselect is documented. In fact I would have thought that it was defeating the advantage of reselect if not for the fact that I'm pretty sure the developers of the framework know what they are doing better then me and that there is a reason for their approach. I'm trying to understand that reason better so I know how to move forward with adding selectors to boilerplate. My confusion is react-boilerplate's exporting methods which call createSelector, instead of exporting an already created selector.
Reselect's documentation has a selectors file which exports already created selectors, and then they call these selectors directly in mapStateToProps. So something like this:
selector.js:
export const basicSelector = (state) => (state.basic.data);
export const fooSelector = createSelector(basicSelector, (state) => (state.get(foo));
export const barSelector = createSelector(basicSelector, (state) => (state.get(foo)));
in component:
function mapStateToProps(state) => ({
foo: fooSelector(state),
bar: barSelector(state),
});
However, react-boilerplate's selector instead exports methods which call createSelector, instead of exporting a created selector directly. So something like this:
selector.js:
export const basicSelector = (state) => (state.basic.data);
export const fooSelector = () => {
return createSelector(basicSelector, (state) => (state.get(foo)));
}
export const barSelector = () => {
return createSelector(basicSelector, (state) => (state.get(foo)));
}
in component:
const mapStateToProps = createStructuredSelector({
foo: fooSelector(),
bar: barSelector(),
});
What is the motivation for calling these dummy methods to create the selector? I would have thought that react-boilerplates approach would mean that if I reuse a selector in different components that each component would have a different instance of the selector; which in turn would mean that each component would have to calculate the result of the selector when state changes rather then it being done once ultimately resulting in redundant calculations.
As I said I suspect i'm missing something, as I doubt a widely used framework would simply fail to use reselect correctly. Could someone explain to me the benefit, and why/if I should maintain react-boilerplate's approach vs doing it the way reselect documentation shows?
What you describe in your second is example is not exactly a selector but a selector factory. It creates and returns a new selector.
Sometimes sharing selectors among different components is tricky, because each component might call the selector with different parameters invalidating reselect cache on each call.
A selector factory avoid this problem by assigning a new different selector to each connected component.
By convention selector factories names usually begin with makelike makeSelectBar or makeGetBar.

Redux: Calling store.getState() in a reducer function, is that an anti pattern?

I'm wondering, sometimes I have a reducer that needs information from another reducer. For example I have this reducer:
import * as ActionTypes from '../actions/action_type_constants';
import KeyCode from 'keycode.js/index';
import {store} from "../index";
import {mod} from "../pure_functions";
export function selectedCompletion(state = 0, action) {
if (action.type === ActionTypes.arrowKeyPressed) {
const completionsLength = store.getState().completions.data.length;
if (action.keyCode === KeyCode.UP) {
return mod(state - 1, completionsLength);
} else if (action.keyCode === KeyCode.DOWN) {
return mod(state + 1, completionsLength);
}
}
return state;
}
I do call store.getState at the second line of the function, because otherwise I can not determine the index correctly.
I could probably refactor this and the other reducer, so that it becomes one big reducer, but for readability I would prefer this option.
I'm not sure if I would get somehow into problems if I use this pattern of calling store.getState() in a reducer.
Yes, this is absolutely an anti-pattern. Reducer functions should be "pure", and only based on their direct inputs (the current state and the action).
The Redux FAQ discusses this kind of issue, in the FAQ on sharing state between reducers. Basically, you should either write some custom reducer logic that passes down the additional information needed, or put more information into your action.
I also wrote a section for the Redux docs called Structuring Reducers, which discusses a number of important concepts related to reducer logic. I'd encourage you to read through that.
The pattern you want is a case of composition because you are preparing new state based in other existing states from other domain (in the sense of reducer domains). In the Redux documentation an example is provided under the topic entitled Computing Derived States.
Notice that their sample, however, combined the states in the container - not in the reducer; yet feeding the component that needs it.
For these coming to this page wondering how to upgrade their large applications to redux 4.0 without revamping their complete statemanagement because getState etc were banned in reducers:
While I, like the authors, dissaprove of that antipattern, when you realize that they banned the usage of these functions without this without any technical reasons and without opt-out, leaving people without updates that have the misfortune of having codebases which broadly use this antipattern... Well, I made a merge request to add an opt-out with heavy guards, but it was just immediately closed.
So I created a fork of redux, that allows to disable the bans upon creating the store:
https://www.npmjs.com/package/free-redux
For anyone else, use one of the examples here or the way I provided in another question:
An alternative way, if you use react-redux and need that action only in one place OR are fine with creating an HOC (Higher oder component, dont really need to understand that the important stuff is that this might bloat your html) everywhere you need that access is to use mergeprops with the additional parameters being passed to the action:
const mapState = ({accountDetails: {stateOfResidenceId}}) => stateOfResidenceId;
const mapDispatch = (dispatch) => ({
pureUpdateProduct: (stateOfResidenceId) => dispatch({ type: types.UPDATE_PRODUCT, payload: stateOfResidenceId })
});
const mergeProps = (stateOfResidenceId, { pureUpdateProduct}) => ({hydratedUpdateProduct: () => pureUpdateProduct(stateOfResidenceId )});
const addHydratedUpdateProduct = connect(mapState, mapDispatch, mergeProps)
export default addHydratedUpdateProduct(ReactComponent);
export const OtherHydratedComponent = addHydratedUpdateProduct(OtherComponent)
When you use mergeProps what you return there will be added to the props, mapState and mapDispatch will only serve to provide the arguments for mergeProps. So, in other words, this function will add this to your component props (typescript syntax):
{hydratedUpdateProduct: () => void}
(take note that the function actually returns the action itself and not void, but you'll ignore that in most cases).
But what you can do is:
const mapState = ({ accountDetails }) => accountDetails;
const mapDispatch = (dispatch) => ({
pureUpdateProduct: (stateOfResidenceId) => dispatch({ type: types.UPDATE_PRODUCT, payload: stateOfResidenceId })
otherAction: (param) => dispatch(otherAction(param))
});
const mergeProps = ({ stateOfResidenceId, ...passAlong }, { pureUpdateProduct, ... otherActions}) => ({
...passAlong,
...otherActions,
hydratedUpdateProduct: () => pureUpdateProduct(stateOfResidenceId ),
});
const reduxPropsIncludingHydratedAction= connect(mapState, mapDispatch, mergeProps)
export default reduxPropsIncludingHydratedAction(ReactComponent);
this will provide the following stuff to the props:
{
hydratedUpdateProduct: () => void,
otherAction: (param) => void,
accountType: string,
accountNumber: string,
product: string,
}
On the whole though the complete dissaproval the redux-maintainers show to expanding the functionality of their package to include such wishes in a good way, which would create a pattern for these functionalities WITHOUT supporting fragmentation of the ecosystem, is impressive.
Packages like Vuex that are not so stubborn dont have nearly so many issues with people abusing antipatterns because they get lost, while supporting a way cleaner syntax with less boilerplate than you'll ever archive with redux and the best supporting packages. And despite the package being way more versatile the documantation is easier to understand because they dont get lost in the details like reduxs documentation tends to do.

redux state selectors in top level reducer

After watching the new egghead course by Dan Abramov, I have question regarding the selectors that was mentioned.
The purpose of the selectors is to hide the details of the state tree from the components, so that it is easy to manage code later if tree changes.
If I understand it correctly, that means, the selectors called inside mapStateToProps should only be the ones that live in the top-level reducer. Because the state that is passed to mapStateToProps is the whole application state tree. If this is true, as the application grows, I can imagine it would become very difficult to manage the top level selectors.
Have I miss understood the concept here? or is this a valid concern?
Edit: trying to make my question clearer.
Say my whole state start with
{ byIds, listByFilter } and I have
export const getIsFetching = (state, filter) =>
fromList.getIsFetching(state.listByFilter[filter]);
in my top level reducer reducers/index.js, and components would simply use getIsFetching passing the whole state to is, which is totally fine because it is the top level.
However, later on, I decided my whole app is going to contain a todo app and an counter app. So it make sense to put the current top level reducers into reducers/todo.js, and create a new top level reducers reducers/index.js like this:
combineReducers({
todo: todoReducer,
counter: counterReducer
})
at the point my state would be like
{
todo: {
byIds,
listByFilter
},
counter: {
// counter stuff
}
}
components can no longer use the getIsFetching from reducers/todo.js, because the state in getIsFetching is now actually dealing with state.todo. So i have to in the top level reducer reducers/index.js export another selector like this:
export const getIsFetching = (state, filter) =>
fromTodo.getIsFetching(state.todo);
only at this point, the component is able to use getIsFetching without worring about the state shape.
However, this raises my concern which is all the selectors directly used by components must live in the top-level reducer.
Update 2: essentially we are exporting selectors from the deepest level all the way up to the top-level reducers, while all the exports in the intermediate reducers are not using them, but they are there because the reducer knows the shape of the state at that level.
It is very much like passing props from parent all the way down to children, while the intermediate component aren't using props. We avoided this by context, or connect.
apologize for the poor English.
So while mapStateToProps does take the entire state tree, it's up to you to return what you'd like from that state in order to render your component.
For instance, we can see he calls getVisibleTodos and passes in state (and params from the router), and gets back a list of filtered todos:
components/VisibleTodoList.js
const mapStateToProps = (state, { params }) => ({
todos: getVisibleTodos(state, params.filter || 'all'),
});
And by following the call, we can see that the store is utilizing combineReducers (albeit with a single reducer), as such, this necessitates that he pass the applicable portion of the state tree to the todos reducer, which is, of course, state.todos.
reducer/index.js
import { combineReducers } from 'redux';
import todos, * as fromTodos from './todos';
const todoApp = combineReducers({
todos,
});
export default todoApp;
export const getVisibleTodos = (state, filter) =>
fromTodos.getVisibleTodos(state.todos, filter);
And while getVisibleTodos returns a list of todos, which by is a direct subset of the top-level state.todos (and equally named as such), I believe that's just for simplicity of the demonstration:
We could easily write another perhaps another component where there's a mapStateToProps similar to:
components/NotTopLevel.js
const mapStateToProps = (state, { params }) => ({
todoText: getSingleTodoText(state, params.todoId),
});
In this case, the getSingleTodoText still accepts full state (and an id from params), however it would only return the text of todo, not even the full object, or a list of top-level todos. So again, it's really up to you to decide what you want to pull out of the store and stuff into your components when rendering.
I also came across this issue (and also had a hard time explaining it...). My solution for compartmentalization this follows from how redux-forms handles it.
Essentially the problem boils down to one issue - where is the reducer bound to? In redux-forms they assume you set it at form (though you can change this) in the global state.
Because you've assumed this, you can now write your module's selectors to accept the globalState and return a selector as follows: (globalState) => globalState.form.someInnerAttribute or whatever you want.
To make it even more extensible you can create an internal variable to track where the state is bound to in the global state tree and also an internal function that's like getStateFromGlobalState = (globalState) => globalState[boundLocation] and uses that to get the inner state tree. Then you can change this variable programatically if you decide to bind your state to a different spot in the global state tree.
This way when you export your module's selectors and use them in mapStateToProps, they can accept the global state. If you make any changes to where the where the reducer is bound, then you only have to change that one internal function.
IMO, this is better than rewriting every nested selector in the top level. That is hard to scale/maintain and requires a lot of boilerplate code. This keeps the reducer/selector module contained to itself. The only thing it needs to know is where the reducer is bound to.
By the way - you can do this for some deeply nested states where you wouldn't necessarily be referring about this from globalState but rather some upper level node on the state tree. Though if you have a super nested state it may make more sense to write the selector from a upper state's POV.

Categories

Resources