redux state selectors in top level reducer - javascript

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.

Related

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

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).

Can useReducer work with an array for state?

I've implemented a fairly simple means to add undo to useReducer with the following:
export const stateMiddleware = reducerFunc => {
return function(state = { history: [] }, action) {
return {
history:
action.type === UNDO
? drop(state.history) // lodash drop
: [reducerFunc(state.history[0], action), ...state.history],
}
}
}
and
const [state, dispatch] = useReducer(stateMiddleware(reducer), { history: [initialState] })
The state variable consists of the entire history so present state will have to be extracted from the first element.
I'm wondering whether useReducer will accept an array for the state parameter (and also welcome to comments on my approach - it avoids using what seem to be hefty packages to do something similar)
If all you are really asking is if you can initialize useReducer hook state with a plain array, then yes, you can.
To give you some feedback on your general approach, I'll quote the Redux docs, as they provide many best practices about global state management. It makes switching easier, if you need a full blown store later on. I'll assume here that you want use the useReducer not just for one local component, but a more global state tree (thanks #DrewReese for the comment on this). If it's only about one component or so, your approach is perfectly fine!
Redux docs concerning basic state shape and wether an array is possible at the top level (Link):
A Redux state usually has a plain Javascript object as the top of the state tree. (It is certainly possible to have another type of data instead, such as a single number, an array, or a specialized data structure, but most libraries assume that the top-level value is a plain object.) The most common way to organize data within that top-level object is to further divide data into sub-trees, where each top-level key represents some "domain" or "slice" of related data.
So basically, its OK, but other libraries could be possibly opiniated and expect an object, as it is by far the most likely choice.
If you implement this shape, be sure that your requirement is to always need an undo action for your whole state. Because it comes with a cost (besides mentioned 3rd party lib compatibility):
adds one additional level of nesting directly at the top level
scalability and granularity (see next point)
What, if your app gets really huge and you have hundreds of reducers. Each time, your action is dispatched, there will be a new state object put in the history array, which could get messy.
What, if you don't want a global undo, but a very fine granular one (in one particular reducer, say for one particular form element in the UI). You would have to distinguish between a global undo and a specific undo then. What instead about just one "UNDO" action which can be handled in each interested reducer in its own manner?
If that still fits your requirements, give it a try !
Cheers
It is possible, but as ford04 pointed out it may not be a great idea for a variety of reasons.
The demo below shows it works to use a plain array as the state in useReducer.
const initialState = [];
function reducer(state, action) {
switch (action.type) {
case 'increment':
return [...state, state.length + 1];
case 'decrement':
return [...state.slice(0, -1)];
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = React.useReducer(reducer, initialState);
return (
<React.Fragment>
<div>
State: {state.join()}
</div>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
</React.Fragment>
);
}
ReactDOM.render(<Counter />, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
<div id="app"></div>

React useReducer Hook fires twice / how to pass props to reducer?

FOREWORD / DESCRIPTION
I am trying to use React's new hooks feature for an e-commerce website that I am building, and have been having an issue working a bug out of my shopping cart component.
I think it is relevant to preface the discussion with the fact that I am trying to keep my global state modular by using multiple Context components. I have a separate context component for the types of items that I offer, and a separate context component for the items in a person's shopping cart.
PROBLEM
The issue I am having is that when I dispatch an action to add a component to my cart, the reducer will run twice as if I had added the item to my cart twice. But only when it is initially rendered, or for weird reasons such as the display is set to hidden and then back to block or for a change in the z-index and potentially other similar changes.
I know this is kind of verbose, but it is rather knit picky issue so I have created two codepens that showcase the issue:
full example
minimum example
You will see that I have included a button to toggle the display of the components. This will help showcase the correlation of the css to the issue.
Finally please monitor the console in the code pens, this will show all button clicks and which part of each reducer has been run. The issues are most evident in the full example, but the console statements display the issue is also present in the minimum example.
PROBLEM AREA
I have pinpointed the problem to be related to the fact that I am using the state of a useContext hook to get the items list. A function is called to generate the reducer for my useReducer hook, but only arises when a different hook is used AKA I could use a function that wouldn't be subject to re-eval like hook is and not have the issue, but I also need the info from my previous Context so that workaround doesn't really fix my issue.
Relevant Links
I have determined the issue is NOT an HTML issue so I will not include the links to the HTML fixes I have tried. The issue, while triggered by css, is not rooted in css so I will not include css links either.
useReducer Action dispatched twice
As you indicated, the cause is the same as the related answer of mine that you linked to. You are re-creating your reducer whenever Provider is re-rendered, so in some cases React will execute the reducer in order to determine whether or not it needs to re-render Provider and if it does need to re-render it will detect that the reducer is changed, so React needs to execute the new reducer and use the new state produced by it rather than what was returned by the previous version of the reducer.
When you can't just move the reducer out of your function component due to dependencies on props or context or other state, the solution is to memoize your reducer using useCallback, so that you only create a new reducer when its dependencies change (e.g. productsList in your case).
The other thing to keep in mind is that you shouldn't worry too much about your reducer executing twice for a single dispatch. The assumption React is making is that reducers are generally going to be fast enough (they can't do anything with side effects, make API calls, etc.) that it is worth the risk of needing to re-execute them in certain scenarios in order to try to avoid unnecessary re-renders (which could be much more expensive than the reducer if there is a large element hierarchy underneath the element with the reducer).
Here's a modified version of Provider using useCallback:
const Context = React.createContext();
const Provider = props => {
const memoizedReducer = React.useCallback(createReducer(productsList), [productsList])
const [state, dispatch] = React.useReducer(memoizedReducer, []);
return (
<Context.Provider value={{ state, dispatch }}>
{props.children}
</Context.Provider>
);
}
Here is a modified version of your codepen: https://codepen.io/anon/pen/xBdVMp?editors=0011
Here are a couple answers related to useCallback that might be helpful if you aren't familiar with how to use this hook:
Trouble with simple example of React Hooks useCallback
React Hooks useCallback causes child to re-render
Seperate the Reducer from the functional component that helped me solve mine
An example based on Ryans excellent answer.
const memoizedReducer = React.useCallback((state, action) => {
switch (action.type) {
case "addRow":
return [...state, 1];
case "deleteRow":
return [];
default:
throw new Error();
}
}, []) // <--- if you have vars/deps inside the reducer that changes, they need to go here
const [data, dispatch] = React.useReducer(memoizedReducer, _data);
When I read some useContext source code, i found
const useContext = hook(class extends Hook {
call() {
if(!this._ranEffect) {
this._ranEffect = true;
if(this._unsubscribe) this._unsubscribe();
this._subscribe(this.Context);
this.el.update();
}
}
After the first time update, a effect like is called after the update. After the value is subscribed to the right context, for instance, resolving the value from Provider, it requests another update. This is not a loop, thanks to _ranEffect flag.
Seems to me if above is true for React, the render engine are called twice.

Bind react component to part of redux state

I have redux store that looks something like this:
{
user: {},
alerts: [],
reports: [],
sourses: []
}
For each one of this parts of state i have a bunch of React Components wrapped in a container wich connected via react-redux. And has mapStateToProps like this
(state) => {alerts: state.alerts}
(state, ownProps) => {alert: _.filter(state, {id: ownProps.curId})}
Problem that when i for example launch some action for Alerts like CREATE_ALERT or EDIT_ALERT and redux state updated, ALL REACT COMPONENTS WILL RESPOND TO THIS CHANGE even ones that works with different parts like sources or reports.
My question: how to "bind" certain components to certain parts of a tree. So each container component WILL UPDATE ONLY WHEN APROPRIATE PART OF REDUX STATE UPDATED and ignore other changes.
Expected behavior
Dispatch CREATE_ALERT -> Alert reducer -> Redux store update -> ONLY Alert container component re-rendering.
When you are changing state in redux the whole state becomes just a new object.
Then your component is given by this new object (new reference) and re-renderes itself.
To fix this behaviour you need to add some logic to compare if your component got props with different value (not reference).
The easiest and fastest way is to use React.PureComponent. You can also override shouldComponentUpdate function and handle changes by yourself. But note that PureComponent works only with primitives (it does a shallow compare).
Check also Immutable.js which helps you with intelligent way of changing references of props.
if you use connect method, then pass only selected redux state to the component, this will prevent rendering of other components
example:
User Component:
const mapStateToProps = state =>({
users: state.users
});
export default connect(mapStateToProps)(User)
Alert Component:
const mapStateToProps = state =>({
alerts: state.alerts
});
export default connect(mapStateToProps)(Alert)
Check this out: Avoid Reconciliation
There explains what Neciu says.
Container components created with connect will always receive notifications of all updates to the store.
The responsibility for consuming these updates falls on the receiving connect component. It should contain the logic to extract the data relevant to it.

React Redux initialState vs defaultProps

I'm looking for the best practices of defining default props for Containers (which is smart components connected with redux store), and I find out that there are at least two approaches how I can realize it.
To use initialState in my reducer:
const initialState = {
name: 'John'
};
export default function userState (state = initialState, action) {...}
To use defaultProps
User.defaultProps = {
name:'John'
};
Which one is the best and why?
You should use the initial state. The concept behind redux and every other library managing the application state is the strict separation of data/model and view. These concepts make it easier to reason about your code, reuse views and test both independently.
If you are using redux I would recommend managing you data (and your default data) inside of redux.
Example of separated test cases:
test('state test', t => {
t.deepEqual(userState(undefined, { type: '##INIT' }), { name: 'John' });
t.deepEqual(
userState({ name: 'John' }, { type: 'SET NAME', name: 'Isa' }),
{ name: 'Isa' }
);
});
test('view test', t => {
t.true(render(<User name="John" />).text().includes('John'));
});
I think you misunderstood two different concepts.
Props in containers/components is just a way to tell component how it should looks or handle some events. But default props shouldn't contain business logic and shared business data, like userInformation.
If you have data like userInformation, which important not only for User container, but also can be useful for other components, store that information only in store.
This explanation helped me to really get the difference between props and state, so I'll leave this here.
Dan Abramov, the creator of Redux, put it this way on Twitter, if I remember correctly:
Should I use component state to store X?
If I can calculate X from props -> No.
If I am not using X in the render method -> No.
Else -> Yes.
Dan was talking about component state here, rather than defaultProps, but the principle in this case is the same.
The point of Redux is to have a single source of truth for your application state. So in your example you would want to store your default values (like name: John) in the Redux store. Props are always passed from the store, but if you specify defaultProps, then you're storing application state outside of the store; it won't be managed by your reducer.
The most important thing is to stay consistent throughout your project.
I think the first approach has the advantage or storing everything related to a store in a single file. Your userState reducer is where someone would go and looks to know how it is updated based on an action type. It seems fair to go there to see how it is initiated too.

Categories

Resources