How should I use selectors in Redux Toolkit? - reactjs

I am learning Redux Toolkit. From a React POV it seems very intuitive to access whatever part of the state you need from within useSelector using an inline arrow function, and then conduct any calculations. As an example consider a cart item with its data (like item count) in redux store.
function CartItemCounter({ itemId }){
const cart = useSelector(state => state.cart);
const itemInCart = cart.items[itemId];
const count = itemInCart?.count || 0;
return <div>{itemId} - {count} nos</div>
}
But I'm seeing all this information saying you should define your selectors beside your slice, use createSelector and all. What is the right way, and why is it better?

The information that is out there is essentially talking about different levels of optimization while using useSelector.
What you need to understand before anything else, is how useSelector works internally.
How does useSelector work?
When you pass a function to useSelector (obviously inside a react component), it essentially hooks on to the global redux state. Whenever any change happens in any part of the global state (i.e. when dispatch() is called from any part of the app), redux will run all the functions you passed to useSelector in your app, and perform certain checks.
Redux will take the result from each function, and compare it to the value it got the last time it ran the same function.
How does it make this comparison?
It uses a reference equality for this comparison. So if redux has to think that the result of the function hasn't changed, either the value returned from the function has to be a primitive and equal.
4 === 4 // true
'itemA' === 'itemA // true
Or, the value returned must be a derived data type (arrays, objects), with the same reference. So essentially the same object.
const x = { name: 'Shashi' }
const fn1 = () => x;
const fn2 = () => x;
const fn3 = () => { name: 'Shashi' }
fn1() === fn2(); // true
fn1() === fn3(); // false, because the objects are different, with different references
In practice, redux changes the wrapping object if either a key (or key of a nested object) is changed, or you manually change the entire object using a dispatch action (This is related to the immer library integration). This is similar to how you would do in regular React.
/* See how most keys are spread in, and will hence maintain reference equality.
While certain keys like 'first', 'first.second', 'first.second[action.someId]'
are changed with new objects, and so will break reference equality */
function handwrittenReducer(state, action) {
return {
...state,
first: {
...state.first,
second: {
...state.first.second,
[action.someId]: {
...state.first.second[action.someId],
fourth: action.someValue,
},
},
},
}
}
Otherwise it maintains the same objects within its state, and returns the exact same objects with the same references, when you access them. To verify this, if you access your cart twice, its literally going to be the same object.
const cart1 = useSelector(state => state.cart)
const cart2 = useSelector(state => state.cart)
cart1 === cart2; // true
What does it do with this comparison?
If the comparison returns true, i.e. the new value is the same as the old value, Redux tells that instance of useSelector to chill tf out, and not do anything. If it returns false however, it tells that component to re-render. After all, the value you are accessing from state has "changed"(according to Redux laws), so you probably want to show the new value.
With this information, we can make change the kind of function we pass to the useSelector, in order to get certain optimization benefits.
Optimization Levels
Level 0: Accessing slice data inline
const cart = useSelector(state => state.cart)
// extract the information you need from within the cart
const itemInCart = cart.items[itemId];
const count = itemInCart?.count || 0;
This is not a good way to access the data. You actually need a subset of the data from the cart, but you are fetching the whole thing, and doing the calculation outside the selector.
Problems:
When you put stuff like this inline, what happens if you change the shape of your data in the future? You have to go to every place that uses useSelector and manually change it. Not so good.
More importantly, every time any part of the cart changes, the entire cart object actually changes. So Redux sees your component that asks for the cart, and thinks
The cart has changed. This component is asking for the cart. It should probably re-render.
BAM Every single instance of this component rerenders. And for what? The count of the item you're referencing probably didn't change. So ideally there shouldn't have been a re-render.
Level 1: Centralize the selector
An easy optimization is to put the selector function in a centralized location next to your slice. That way, if your data shape changes in the future, you can just change it in one place, and your whole app (wherever it uses that data) will work with the new shape.
// inside or next to the slice file
const selectCart = (state) => state.cart
//...
// somewhere inside a react component
const cart = useSelector(selectCart)
Level 2: Access the relevant data
Since redux is comparing the results of your selector function, if you want to avoid unnecessary rerenders, you want to make sure the results have reference equality (===). So target the exact value you wish to look at, in your selector.
// extract the information you need from within the cart, *within the selector*
const count = useSelector(state => state.cart.items[itemId]?.count || 0)
// You don't have to use a one-liner, a multi-line function is better for readability
When Redux executes these functions, it keeps a record of the value returned from these selector functions, for each individual useSelector. This time the values are going to be the same for every single counter, except the one that actually changed. All those other counters that didn't actually change in value don't have to unnecessarily re-render anymore!
And if any of you folks think this is premature optimization, the answer's no. This is more along the lines of putting a dependancy array on your useEffects to avoid infinite loops.
Not forgetting the Level 1 optimization, we can also extract this function centrally
const selectItemById = (state, itemId) => (state.cart.items[itemId]?.count || 0);
function CartItemCounter({ itemId }){
//...
// somewhere inside a react component
const count = useSelector((state) => selectCart(state, itemId))
//...
}
So that solves all of our problems right?
For now, yes. But what if this selector function has to run some expensive computation.
const selectSomething = (state) => reallyExpensiveFn(state.cart)
//...
// somewhere inside a react component
const cart = useSelector(selectSomething)
You don't want to keep running that do you?
Or what if you have no option but to return new objects from your select function. A common scenario for this case would be returning a subset of data from the state.
const selectFilteredItems = (state) => state.itemsArray.filter(checkCondition) // the filter method will always return a new array
//...
// somewhere inside a react component
const cart = useSelector(selectFilteredItems) // re-renders every time
To solve this you would have to memoize or cache the results from the function call. Essentially you would need to make sure that if the input arguments are the same, the result will maintain reference equality with the previous result. This introduces the need to maintain some kind of cache state.
Level 3: createSelector
Fortunately, Reselect library, which is reexported with Redux Tookit, does this work for you. You can take a look at the redux toolkit for the syntax.
const selectFilteredItems = createSelector(
(state) => state.itemsArray, // the first argument accesses relevant data from global state
(itemsArray) => itemsArray.filter(checkCondition) // the second parameter conducts the transformation
)
//...
// somewhere inside a react component
const cart = useSelector(selectFilteredItems) // re-renders only when needed
Here the second function is called the transformation function, and is where we put the expensive computation, or the function that returns inconsistent references as a result (filter,map etc).
The createSelector caches
a) the arguments to the transformation function
b) the result of the transformation function
of the previous call to the selectFilteredItems function. If the arguments are the same, it skips executing the transformation function, and gives you the result you got the last time it was executed.
So when useSelector looks at the result, it gets reference equality. Hence the re-render is skipped!
One little caveat here is that createSelector only caches the very previous result. This makes sense if you think about a single component. In a single component you are only concerned about differences in values and results compared to the previous render. But in practice, you are likely to share selectors across multiple components. If this happens, you have a single cache location, and multiple components using this cache. i.e. Stuff breaks.
Level 4: createSelector factory function
Since the logic for your selector is the same, what you need to do is run createSelector for each component that uses it. This creates a cache for each component, giving us the desired behaviour. In order to do this, we use a factory function.
const makeSelectFilteredItems = () => createSelector(
(state) => state.itemsArray, // the first argument accesses relevant data from global state
(itemsArray) => itemsArray.filter(checkCondition) // the second parameter conducts the transformation
)
//...
// somewhere inside a react component
const selectFilteredItems = useMemo(makeSelectFilteredItems,[]); // make a new selector for each component, when it mounts
const cart = useSelector(selectFilteredItems) // re-renders only when needed
You intend to make a new selector (and by extension, cache) for each new component that mounts. So you put it inside the actual component function and not on the module scope. But this will re-run makeFilteredSelector for each render, and hence create a new selector for each render, and hence eliminate the cache. This is why you need to wrap the function in a useMemo with an empty dependency array. It runs on every mount.
And voila!
You now know where, why and how to use selectors in Redux. I personally feel that the createSelector syntaxes are slightly contrived. There is some discussion on changing cache sizes going on. But for now I feel that sticking to the docs should get you through most situations.

But I'm seeing all this information saying you should define your selectors beside your slice, use createSelector and all.
That's the way to go if you're deriving something from the state, which ends up being an expensive computation or something that's reused often throughout your app. Imagine, for example, your state.cart can contain 50.000 items and you need to sort them from most expensive item to least expensive. You don't want to re-calculate this all the time because it slows your app down. So you cache/memoize the result.
What is the right way, and why is it better?
The right way is to use memoization helpers like createSelector when/if you want to avoid expensive computation. Most people optimize prematurely, so I'd just stick to useSelector and keep it simple if in doubt.

Related

Alternative way to handle state in React using memo

When it comes to handling complex state in React everybody suggests to flatten the state to avoid something like this just to update a single property:
setState({ …state, user:{ …state.user, profile:{…state.user.profile, address:{…state.user.profile.address, city:’Newyork’}} }});
Which is really cubersome to work with. There is another way: use an object holding your state and return that from a memoized function. Then whenever you made a change simply force a re-render.
// note the reference cannot be changed, but values can.
const data = useMemo(() => ({
user: {
name: "",
profile: {
address: {
city: "New york"
}
}
}
}), []);
// use dummy data to trigger an update
const [toggle, setToggle] = useState(false);
function forceUpdate() {
setToggle(prev => !prev);
}
function makeChanges() {
// make any change on data without any copying.
data.user.address.city = "new city name";
// hydrate the changes to the view when you're done
forceUpdate();
}
return (
<div onClick={() => makeChanges()}>{data.user.address.city }</div>
)
Which works perfectly. Even with massive and complex data structures.
From what I can tell state is really just a memoized values which will trigger an update upon change.
So, my one question: What is the downside of using this?
The docs say useMemo is not a guarantee:
You may rely on useMemo as a performance optimization, not as a semantic guarantee. In the future, React may choose to “forget” some previously memoized values [...]
If you'd really want to do something like this and you are absolutely positively sure you're willing to do things unlike anyone else in React land, you'd use useRef for state storage that doesn't cause rerenders by itself. I'm not going to add an example of that, because I don't recommend it in the least.
You should also note that your method will not cause memoized (React.memo()) components to rerender, since they will not "see" changes to props if their identity does not change. Similarly, if another component uses one of your internally mutated objects as a dependency for e.g. an effect, those effects will not fire. Finding bugs caused by that will be spectacularly annoying.
If modifying deep object structures is otherwise cumbersome, see e.g. the Immer library, which does Proxy magic internally to let you modify deep objects without trouble – or maybe immutability-helper if you're feeling more old-school.

Why Call React Hooks at the Top Level?

I was reading the React-hooks concepts. I came through a rule which says Don't call React hooks inside conditions.
Here they provided the explanation link.
function Form() {
// 1. Use the name state variable
const [name, setName] = useState('Mary');
// 2. Use an effect for persisting the form
useEffect(function persistForm() {
localStorage.setItem('formData', name);
});
// 3. Use the surname state variable
const [surname, setSurname] = useState('Poppins');
// 4. Use an effect for updating the title
useEffect(function updateTitle() {
document.title = name + ' ' + surname;
});
// ...
}
I understood what they want to say, but I can not get the exact reason, like why I can't use useEffect in if-else block?
There is one more statement
So how does React know which state corresponds to which useState call?
The useState is different call every time and it can return new "[state, setState]" each time, so what is difficult here to know who called which useState?
Bassically, hook rely on a call index. React doesn't know what a given useState() returned as it's state the previous render, but it does know that the first call to useState() by that component returned a [1,2] as it's value, and the second call returned false. Now, if the only thing react knows is what was the given return for a given call index, what do you think could happen if i could write a componente like this:
const [a, setA] = React.useState([1,2,3]);
let c;
if(a === [3,2,1]){
c = React.useState('X');
}
const [b, setB] = React.useState(false);
React.useEffect(() => setA([3,2,1]), []);
now, react knows from the first render that the first call returns [1,2,3] and the second false. then the effect rerenders the component, now it's not the first render so the first call will return the state [3,2,1] since it was updated, the second call (the one c = ...) will return false, but then react sees a third call, what should it return?
From react's point of view, this makes no sense, from your point of view, this can lead to an enormous amout of bugs and problems.
Of course, neither my very basic explanation nor React's are a lot, that's why a come bearing sources, Dan Abramov, one of the people working in react has a very long and detailed post on his blog about this, you can find it here. He also posts a lot of other stuff about how react works behind the curtains, it's worth the read.
It is not about who called which hook useXXXX(i.e useState, useEffect, etc). It is about how hooks are internally implemented and associated with each component. There are a lot of other problems which to solve React relies on the call order of the hooks.
From the docs Hooks FAQ section
How does React associate Hook calls with components?
There is an internal list of “memory cells” associated with each component. They’re just JavaScript objects where we can put some data. When you call a Hook like useState(), it reads the current cell (or initializes it during the first render), and then moves the pointer to the next one. This is how multiple useState() calls each get independent local state.
Internally hooks are implemented like a queue where each hook represents a node having reference to the next one. The internal structure might look something similar to this,
{
memoizedState: 'a',
next: {
memoizedState: 'b',
next: null
}
}
Take the example of having 4 state variables by calling useState 4 times. With each hook call if the value has not been initialized(i.e on first render) it will initialize the value else read from the memory cell then moving to the next hook internally.
// 4 independent local state variables with their own "memory cell"
// nothing is called conditionally so the call order remains the same
// across renders
useState(1) // 1st call
useState(2) // 2nd call
useState(3) // 3rd call
useState(4) // 4th call
useState(1)
if (condition) { // if condition false hook call will be skipped
useState(2)
}
useState(3)
useState(4)
Now when you call a hook conditionally if the condition is false the hook call will be skipped. This means every subsequent hook call will shift by 1 in the call order resulting in failure to read state value or replacing an effect or many more hard to detect bugs.
So in general it is a bad idea to call any hook conditionally. Only call the hook in top-level(not inside condition, nested functions, or loops) which will help React to preserve the state of hooks for multiple hook calls.
From this answer of React document, it mentioned that hooks are store in a "memory cells" and render in order (" moves the pointer to the next one")
There is an internal list of “memory cells” associated with each
component. They’re just JavaScript objects where we can put some data.
When you call a Hook like useState(), it reads the current cell (or
initializes it during the first render), and then moves the pointer to
the next one. This is how multiple useState() calls each get
independent local state.
Which is match with below section of the link you provided which has some more explanation
// First render
// ------------
useState('Mary') // 1. Initialize the name state variable with 'Mary'
useEffect(persistForm) // 2. Add an effect for persisting the form
useState('Poppins') // 3. Initialize the surname state variable with 'Poppins'
useEffect(updateTitle) // 4. Add an effect for updating the title
// ------------- // Second render // -------------
useState('Mary') // 1. Read the name state variable (argument is ignored)
useEffect(persistForm) // 2. Replace the effect for persisting the
form
useState('Poppins') // 3. Read the surname state variable
(argument is ignored)
useEffect(updateTitle) // 4. Replace the
effect for updating the title
In the second render section, the docs said Read the ... variable means when a useState called in the second time, it doesn't generate new [state, setState], it comes to the "memory cells" to read state value instead and return then we assign it to new array by const [state, setState] = useEffect(). That's why React can guarantee that setState will not be changed each re-render
React guarantees that setState function identity is stable and won’t
change on re-renders. This is why it’s safe to omit from the useEffect
or useCallback dependency list.

"Reselect" VS "Memoize-One" with "React and Redux"

I'm trying to use some kind of memoization in my workflow with React, and I'm searching for the best and most importantly the "easiest" solution to integrate with my workflow that includes React and Redux.
I came across many articles talking about memoization in general and some demonstrate the use of "memoize-one" and brace it as the fastest and easiest to get up and running with, and others don't even mention it and talk about "reselect".
I just want to know which is better and easiest and which should I invest in.
Both libraries return a function which accepts a given numbers of arguments and returns a value:
getA(arg1, arg2, arg3) // Returns a value
The difference lays in what happens under the hoods when the function is called.
memoize-one
collect provided arguments
compare arguments with the ones provided in previous call (===)
arguments are the same: return cached result
arguments are NOT the same: re-evaluate result function and return
reselect
collect provided arguments
run a set of inputSelectors function providing them with the collected arguments
collect inputSelectors return values
compare inputSelectors return values with the ones obtained in previous call (===)
values are the same: return cached result
values are NOT the same: re-evaluate result function and return
Conclusions
memoize-one is a value-based memoize utility: memoization is performed over the value of provided arguments.
reselect adds a further evaluation layer on top of it: memoization is NOT performed over arguments values BUT over the results of a set inputSelectors functions fed with those initial arguments.
It means that reselect selectors are easily composable since each inputSelectors can be another reselect selector.
I haven't used reselect, but memoize-one works great for me when I want to calculate something from props inside render. This is a great pattern for doing an expensive operation, like mapping a large array, on props that may change over time but also may not change on some re-renders. It ensures an expensive operation used in render is re-computed only when the inputs change. It also avoids having to use lifecycle methods like getDerivedStateFromProps (if it can be calculated from props, it probably shouldn't be on state).
import memoize from 'memoize-one'
class Example extends Component {
mapList = memoize(
(list) => list.map(item => ({text: item.text}))
)
render() {
// if this.props.list hasn't changed since the last render
// memoize-one will re-use the last return value from cache
const mappedList = this.mapList(this.props.list)
return (
...
)
}
}
Keep in mind, in most cases, you’ll want to attach the memoized function to a component instance vs. using a static class variable. This prevents multiple instances of a component from resetting each other’s memoized keys.
react memoization reactjs
I suggest to use reselect, since it was specifically designed to use with React/Redux. memoize-one is more like a general purpose memoization library.
It's really easy to use reselect, it just wraps your selectors:
import { createSelector } from 'reselect';
const shopItemsSelector = state => state.shop.items;
// without reselect
const subtotalSelector = state => {
const items = shopItemsSelector(state);
return items.reduce((acc, item) => acc + item.value, 0);
}
// with reselect
const subtotalSelector = createSelector(
shopItemsSelector, // list the selectors you need
items => items.reduce((acc, item) => acc + item.value, 0) // the last argument is actual selector
)

reselect memoization ignore irrelevant updates

Following the example on the reselect docs:
import { createSelector } from 'reselect'
const shopItemsSelector = state => state.shop.items
const subtotalSelector = createSelector(
shopItemsSelector,
items => items.reduce((acc, item) => acc + item.value, 0)
)
In a typical redux app, subtotalSelector will recompute if a user updates item.name, even though this has no impact on the result. Is there a way to avoid this?
Two solutions:
Let it be. Unless you have a large number of items, the browser's computing capacity is well enough to handle the recompute.
Seperate prices from the item object. That is, you have state.shop.items.itemNames(containing id-name pairs) and state.shop.items.itemValues(containing id-value pairs). Then only the itemValues is passed to the selector.
I have a similar problem and I have found a sort of hack to get arround it.
I have a complex set of filters, and a huge number of items to filter through. Part of the filter state includes display state. I want to ignore changes in the display state so I don't filter a huge list all the time. This is an easy-ish solution:
const getFilters = createSelector(
state => state.filters,
filters => {
const filtersWithoutDisplay = {};
const ignoreObj = { collapsed: null };
for (let filterGroup in filters) {
filtersWithoutDisplay[filterGroup] = Object.assign({}, filters[filterGroup], ignoreObj);
}
// We create a new object every time, so this cannot be memoized properly unless we stringify.
return JSON.stringify(filtersWithoutDisplay);
}
);
It returns a JSON string that has to be parsed, but it's a primitive so as an input to another selector it doesn't trigger a recomputation if the actual contents don't change. That's kind of a hack.
You could also define an object outside of the selector function and always keep the same reference, change the insides according to this same patter, and then use a custom deep equality check by pulling in createSelectorCreator, as explained here https://github.com/reactjs/reselect#q-why-is-my-selector-recomputing-when-the-input-state-stays-the-same . This is probably a better way to go, but as it says:
Always check that the cost of an alternative equalityCheck function or deep equality check in the state update function is not greater than the cost of recomputing every time.
That goes for the JSON.stringify hack as well. I wouldn't do it for the huge list, but for the filters, sure.
In my situation, it's probably better to refactor my state because the filter values may be a separate concern from the filter display settings, and this may not be the only time I want them separate.

Avoid re-rendering a big list of items with react-redux

I am using redux with react and typescript for my application. I am working with many items used at different places of my app. My state looks like this:
{
items: {42: {}, 53: {}, ... }, //A large dictionary of items
itemPage1: {
itemsId: [ 42, 34, 4 ],
...
},
itemPage2: { ...
},
...
}
The user can modify some attributes of the items dispatching some actions. When this happen I need to redraw the components that have been modified in each pages. The issue is that my items are quite big and I cant afford to redraw all of them at each small modification. I was wondering is this approach would work:
I have a fist component <ItemPage1> which connects to the store to get all of the states stored in the tree under itemPage1 e.g. the list of items id: itemsId.
Inside <ItemPage1>, I loop over the itemsId property to generate multiple FilterItem components: itemsId.map( itemId => return <FilterItem id=itemId>);
Finally each Item is connected using ownProps to get the correct part of the state:
const mapStateToItemProps = (state, ownProps) => {
return {
item: state.items[ownProps.id],
}
}
const mapDispatchToItemProps = (dispatch, ownProps) => {
return null;
}
const FilterItem = connect(
mapStateToItemProps,
mapDispatchToItemProps
)(Item)
Can you confirm or refute that if I update the item of id 42, then only this item is going to be re-rendered ?
When rendering big list you need to take into considerations few things :
Lower the total number of DOM elements that you need to render (by not rendering items that are not actually visible on the screen, also known as virtualization)
Don't re-render items that have not changed
Basically, what you want to avoid is a complete re-render of your list (or your page) when the user edits one single row. This can be achieved exactly how you did it, i.e : by passing to the list container only the ids of items that need to be rendered, and to map over these ids to connect each component by using ownProps. If you have a dump <Item/> component, your <ItemPage/> component will create connected connect(<Item/>) component.
This is going to work, if your put a console.log('item rendered') in your <Item/> component class you will notice that there is only one call.
BUT (and it's a big but), what is not obvious when working with react-redux is that all connected components that depends on their ownProps will always rerender if any part of the state change. In your case, even if the <Item/> components will not re-render, their wrapped component connect(Item) will ! If you have few dozens of items, you might encounter some latency if actions need to be dispatched quickly (for example when typing in an input). How to avoid that ? Use a factory function to use ownProps as the initial props :
const mapStateToItemProps = (_, initialProps) => (state) => {
return {
item: state.items[initialProps.id], // we're not relying on the second parameters "ownProps" here, so the wrapper component will not rerender
}
}
const mapDispatchToItemProps = (dispatch, ownProps) => {
return null;
}
const FilterItem = connect(
mapStateToItemProps,
mapDispatchToItemProps
)(Item)
I suggest you to take a look to this other answer.
You might also be interested in these excellent slides : Big List High Performance React & Redux
And finally, you should definitively take a look to react-virtualized to perform the virtualization of your list (i.e, displaying only the item that the user can actually see).
Ok, I've found this discussion: https://github.com/reactjs/redux/issues/1303
At the bottom it is clearly stated (from multiple protagonists):
[...] react-redux takes care of this. It lets you specify specific parts of the state you care about, and takes care to bail out of updating React components when the relevant parts have not changed.
[...] Just wanted to fully understand that what's going on under the hood here, So if the Redux store gets updated but one specific component state hasn't changed, Redux won't trigger the forceUpdate() method for that component? [...]
The wrapper component generated by React-Redux's connect() function does a several checks to try to minimize the number of times your actual component has to re-render. This includes a default implementation of shouldComponentUpdate, and doing shallow equality checks on the props going into your component (including what's returned from mapStateToProps). So yes, as a general rule a connected component will only re-render when the values it's extracting from state have changed.
So I believe my implementation is good, it won't re-render all the items since only one item will see its properties modified.

Resources