I'm confused about the arguments that i passe to a selector function that uses reselect inside containers - reactjs

I'm learning the usage of reselect, but i'm confused about the way i make use of the memoized selector in inside my container.
I've got a component called Summary that received 3 props thats are subtotal, tipAmount , total .
It looks like this export const Summary = ({ subtotal = 0, tipAmount = 0, total = 0 })=>{...}.And those props are being inject my the connect HOC inside a dedicated container that i've called SummaryContainer with the following code.
import { connect } from 'react-redux';
import { Summary } from '../components/Summary';
import {
selectTipAmount,
selectSubtotal,
selectTotal
} from '../store/items/selectors';
const mapStateToProps = (state) => {
const subtotal = selectSubtotal(state);
const tipAmount = selectTipAmount(state);
const total = selectTotal(state);
return {
subtotal,
tipAmount,
total
};
};
export const SummaryContainer = connect(mapStateToProps)(Summary);
As you can see this code uses 3 selectors selectTipAmount , selectSubtotal and selectTotal that i'm importing from a selector file with the follwing code.
import { createSelector } from 'reselect';
const selectItems = (state) => state.items;
const selectTipPercentage = (state) => state.tipPercentage;
export const selectSubtotal = createSelector([selectItems], (items) =>
items.reduce((acc, { price, quantity }) => acc + price * quantity, 0)
);
export const selectTipAmount = createSelector(
[selectSubtotal, selectTipPercentage],
(subtotal, tipPercentage) => subtotal * (tipPercentage / 100)
);
export const selectTotal = createSelector(
[selectSubtotal, selectTipAmount],
(subtotal, tipAmount) => subtotal + tipAmount
);
export const selectItemWithTotal = createSelector([selectItems], (items) =>
items.map((item) => ({ ...item, total: item.price * item.quantity }))
);
export const selectItem = (state, props) =>
state.items.find((item) => item.uuid === props.uuid);
export const selectItemTotal = createSelector(
[selectItem],
(item) => item.price * item.quantity
);
So here is my problem :
When i call write const total = selectTotal(state);inside mapStateToProps i passe to the total constant the results of the execution of selectTotal(state) selector and when i look at the argument, it's state.It looks like i'm invoking the selectTotal selector by passing and state argument, but in the actuel implementation it isn't a function that takes a state param.In the actual implementation that selection equals the result of createSelect function like this:
export const selectTotal = createSelector(
[selectSubtotal, selectTipAmount],
(subtotal, tipAmount) => subtotal + tipAmount
);
I haven't seen where the state argument is being passed.What is going on?
The code works perfectly but i'm lost about where the state argument is being used.
When i look at selectItems selector it makes sens, but with selectors that are created with createSelector i'm lost.

I find it easiest to think of reselect selectors as sort of the "opposite" of redux reducer functions. With reducer functions each reducer owns a chunk of state and when they are combined they form a reducer tree where once they are all combined form the entire state object.
The selectors are basically doing the opposite. They start with simple selectors that take the entire state object and pluck out chunks of it. These sub-state chunks can then be fed into created selectors to pluck out smaller chunks (or compute derived state). The inputs used in createSelector are a sort of "dependency", when one of them updates then the selector recomputes its value.
createSelector is essentially a higher order function that consumes "dependencies" and a result function, and returns a function that consumes the state object. When you call selectSubtotal and pass state you are really passing state to a selector tree (or set of trees).
For example, when state.items updates the selectItems selector that is an input to selectSubtotal will update its return value. This update will trigger selectSubtotal to then recompute its value, and so on for any other selectors that use selectSubtotal as an input.
If you are curious, the resultant selector function will also have a resultFunc property that matches the computing function originally passed to createSelector. For example, selectTipAmount.resultFunc will be (subtotal, tipPercentage) => subtotal * (tipPercentage / 100), called like selectTipAmount.result.Func(subTotal, tipPercentage);. I use this heavily when unit testing my selectors since I really don't care about memoizing a value, but rather that they correctly compute derived state; I won't need to mock up an entire state object that matches the selector dependencies.

The createSelector function returns a function so in const mySelectorFunction = createSelector... the value mySelectorFunciton is a function.
A simple implementation of createSelector without memoization would be:
const createSelector = (functions=[],calculatorFunction) => {
// you could do memoize(calculatorFunction) to implement memoization
//the function returned is the selectOnePlusTwo in demo below
return (...args) =>
// when this function is called then it will call functions with args
// and pass the returning values to calculatorFunction
calculatorFunction(...functions.map(fn=>fn(...args)))
}
// demo createSelector
const state = { one:1,two:2 }
const selectOne = state=>state.one
const selectTwo = state=>state.two
const selectOnePlusTwo = createSelector(
[selectOne,selectTwo],
(one,two)=>one+two
)
console.log(
'one plus two:',
selectOnePlusTwo(state)
)
Memoization is a nice touch that could make your code slightly more performant by skipping virtual DOM compare but what usually causes havoc on your performance is passing in a new handler reference that will cause this comparison to fail and make React re paint that DOM, for example <SomeComponent onClick={()=>newRefCreated} /> will re paint whatever SomeComponent generates because the onClick changes every time. This is why there is a useCallback hook.
I'd say that the power of createSelector comes from easily re usable code and preventing duplicate implementation. If in the example above state.one would change to a string I need to only refactor the selectOne function and all other selectors depending on state.one would still work.

Related

Adding Redux State Values

I'm trying to combine two separate prices into one total price. I have the two individual price states stored and correctly updating using independent reducers, but I need a way to combine these two dynamic values into a third, total price state.
import React, {useState} from "react";
import { useSelector, useDispatch } from "react-redux";
const planPrice = useSelector(state => state.planPrice);
const repairPrice = useSelector(state => state.repairPrice);
//Use the state hook to store the Total Price
const [totalPrice, setTotalPrice] = useState(0);
const [items, setItems] = useState([]);
function addItem (newItem) {
setItems((prevItems) => {
return [...prevItems, newItem];
});
//Update the Plan Price
if ({plan}.plan === "Premium") {
dispatch(addItemPremium());
} else if ({plan}.plan === "Basic") {
dispatch(addItemBasic());
}
//Update the Repair Price
if (newItem === "Small Chip") {
dispatch(addSmallChip());
} else if (newItem === "Big Chip") {
dispatch(addBigChip());
}
// //Update the Total Price
setTotalPrice({planPrice} + {repairPrice});
}
Is this even possible using Redux, or am I going about this the wrong way?
If you have derived state, you should consider using selectors. These are functions that take state as an argument and return a derived state value.
For example:
function totalPrice(state) {
return state.price1 + state.price2;
}
If you need to use this in mapStateToProps, you can do so as follows:
const mapStateToProps = state => ({
price: totalPrice(state)
})
If you're using hooks, you can use the useSelector hook:
const price = useSelector(totalPrice);
There are libraries (e.g., reselect) that will help you create composable selectors with caching for efficiency.
Note that the reason you should use selectors versus storing redundant state, as you may have intuited, is that redundant state can get out of sync and then you're hosed. But if you have a function that computes the total price, it'll always be in sync with the individual state parts.

Reselect error: Selector creators expect all input-selectors to be functions

I'm trying to use reselect within my component:
const test = useSelector(({ test }) => test);
const testData = createSelector(
test,
items => console.log(items),
);
I'm getting Error: Selector creators expect all input-selectors to be functions, instead received the following types: [object]. I don't know if it is important but test comes async.
What can I do to make it work?
Thank you!
When processing JavaScript, the interpreter will read all functions in scope before executing any statements.
It's important to note that if you use the ES6 style of defining functions as lambdas
then your "functions" don't get processed until those assignment statements are executed.
instead of
const makeSelectUserEmail = () => {
createSelector(
selectAuthDomain,
authState => authState.email,
);
}
use
const makeSelectUserEmail = () =>
createSelector(
selectAuthDomain,
authState => authState.email,
);
Your code appears to be kind of backwards.
useSelector expects a selector function as its argument, calls the selector, and returns the result.
That snippet appears to be trying to use the return value from useSelector as an input to createSelector, which is wrong.
In addition, you shouldn't call createSelector inside of a component, as that will create a new memoized selector instance every time the component renders.
Finally, while I realize this is likely just an attempt to verify things are working, you shouldn't be doing work like console.log() inside of a selector. A selector should just return a value.
If this test value doesn't exist right away, then your selector and component logic should be written in a way that handles cases where it may not exist.
I had the same issue, but based on the react-redux documentation, this is how you should use Reselect with useSelector:
import React from 'react'
import { useSelector } from 'react-redux'
import { createSelector } from 'reselect'
const selectNumCompletedTodos = createSelector(
(state) => state.todos,
(todos) => todos.filter((todo) => todo.completed).length
)
export const CompletedTodosCounter = () => {
const numCompletedTodos = useSelector(selectNumCompletedTodos)
return <div>{numCompletedTodos}</div>
}
export const App = () => {
return (
<>
<span>Number of completed todos:</span>
<CompletedTodosCounter />
</>
)
}

Redux, Reselect and ImmutableJS causing unnecessary renders on child components

Based on all the Redux and Reselect docs I have just read and re-read the below selector should only do the thing.toJS() processing if the Immutable Map that getThing() returns is not equal to the previous one.
...
// Selector
import { createSelector } from 'reselect'
const getThing = (state, thingId) => {
return state.entities.getIn(['things', thingId])
}
const makeThingSelector = () => {
return createSelector(
[getThing],
(thing) => {
return thing.toJS()
}
)
}
export default makeThingSelector
...
// Container
const makeMapStateToProps = () => {
return (state, ownProps) => {
const { thingId } = ownProps
const things = select.makeThingsSelector()(state, thingId)
return {
hasNoThings: things.length === 0,
things
}
}
}
const Container = connect(
makeMapStateToProps,
mapDispatchToProps
)(Component)
...
This holds true unless I have a child 'smart' component. In this case, when the parent triggers a render, the selector called in the child component's container always processes the value regardless of whether the result is new or not.
I have been trying to encapsulate the ImmutableJS API inside my selectors but this means to avoid a re render on these nested components every time their parents update I have to do a deep equality check in the shouldComponentUpdate function. This is expensive and doesn't seem like a decent solution.
The app state is normalised so the updated part of the state tree is not a hierarchical parent to the part of the state that the child component is dependent on.
Am I missing something key here?
On every store update react-redux performs following steps (putting all internal complexities aside):
Calls mapStateToProps and mapDispatchToProps.
Shallowly comparing resulted props
Re-renders Component In case new props differs from previous one.
This way mapStateToProps will be called on every store update by-design. So will following line of code:
...
const things = select.makeThingsSelector()(state, visitId)
...
As you can see new reselect selector will be created every time effectively preventing any memoization (there are no global state in reselect, memoization happens per selector).
What you have to do is change your code so that one and the same selector will be used on every invocation of mapStateToProps:
const thingSelector = select.makeThingsSelector();
...
const makeMapStateToProps = () => {
return (state, ownProps) => {
const { visitId } = ownProps
const things = thingSelector(state, visitId)
return {
hasNoThings: things.length === 0,
things
}
}
}
UPDATE: Also I don't see any reason to use factory-style makeThingsSelector and makeMapStateToProps. Why not just go with something like:
...
// Selector
export default createSelector(
[getThing],
(thing) => thing.toJS()
);
...
// Container
const mapStateToProps = (state, ownProps) => {
const { visitId } = ownProps
const things = select.thingsSelector(state, visitId)
return {
hasNoThings: things.length === 0,
things
}
}
const Container = connect(
mapStateToProps,
mapDispatchToProps
)(Component)
...
Since the redux state in this application uses the ImmutableJS data structure, Reselect may not be necessary.
Firstly, ImmutableJS manipulates only the slice of the data structure affected by a change operation and therefore all changes to the larger state may not affect the slice being passed to the container.
Secondly, the redux connect function returns a pure container by default and upon encountering same slice will not re-render. However, the mapStateToProps will be invoked since the whole state and possibly the ownProps have changed.
For finer control, the rendering of same container can be linked directly to changes to a particular slice of the state and ownProps by adding areStatesEqual and areOwnPropsEqual predicate properties to the fourth parameter of the connect function (better known as the options object).
const mapStateToProps = ({ entities }, { thingId }) => {
const things = entities.getIn(['things', thingId]).toJS();
return {
hasNoThings: things.length === 0,
things
};
};
const Container = connect(
mapStateToProps,
mapDispatchToProps,
undefined, {
areOwnPropsEqual: (np, pp) => np.thingId === pp.thingId,
areStatesEqual: (ns, ps) => ns.entities.get(‘things’).equals(
ps.entities.get(‘things’)
)
}
)(Component);
If both of these predicates are true, not only would the container and its children not re-render, the mapStateToProps would not even be invoked!

React-redux rerenders when creating arrays

I have a connected component where I would like to retrieve an array of objects. In my store, there is an array of ids, and an object where I keep the items like this:
const state = {
items: [0, 1, 2],
itemsById: {
0: {},
1: {},
2: {},
},
}
So using the connect function of react-redux, I do this in my component to inject the correct array:
const mapStateToProps = (state) => ({
items: state.items.map((itemId) => state.itemsById[itemId]),
})
My app triggers updates very often (I dispatch actions in requestAnimationFrame), but the items array do not change during the process. By analyzing the app with the React Perf addon, it seems that my connected component makes unnecessary rendering, and I don't understand why because the items in the state don't change.
I already tried to make a memoized selector using reselect but it seems it does not change anything.
Update (solution)
It works when you use a selector created with reselect. My problem was in the selector itself: my items array is located in a parent object which updates very frequently, and I was selecting this object instead of selecting the items array directly.
Don't do this:
const parentSelector = (state) => state.parent
const itemsByIdSelector = (state) => state.itemsById
const selector = createSelector(
parentSelector,
itemsByIdSelector,
(parent, itemsById) => parent.items.map(...)
)
Do this:
const itemsSelector = (state) => state.items
const itemsByIdSelector = (state) => state.itemsById
const selector = createSelector(
itemsSelector,
itemsByIdSelector,
(items, itemsById) => items.map(...)
)
You are creating a new array every time connect is called by doing this:
const mapStateToProps = (state) => ({
items: state.items.map((itemId) => state.itemsById[itemId]),
})
To prevent that use a memoized selector, which will return the same array every time, unless something actually changed. A selector is a method that computes the derived data from the state. Reselect is a memoized selector library for redux.

What are selectors in redux?

I am trying to follow this code in redux-saga
export const getUser = (state, login) => state.entities.users[login]
export const getRepo = (state, fullName) => state.entities.repos[fullName]
Which is then used in the saga like this:
import { getUser } from '../reducers/selectors'
// load user unless it is cached
function* loadUser(login, requiredFields) {
const user = yield select(getUser, login)
if (!user || requiredFields.some(key => !user.hasOwnProperty(key))) {
yield call(fetchUser, login)
}
}
This getUser reducer (is it even a reducer) looks very different from what I would normally expect a reducer to look like.
Can anyone explain what a selector is and how getUser is a reducer and how it fits in with redux-saga?
getUser is not a reducer, it is indeed a selector, that is, a function that knows how to extract a specific piece of data from the store.
Selectors provide an additional layer such that if you altered your store structure and all of a sudden your users were no longer at state.entities.users but instead at state.users.objects.entities (or whatever) then you only need to update the getUser selector and not every place in your app where you were making a reference to the old location.
That makes them particularly handy when it comes to refactoring your Redux store.
Selectors are getters for the redux state. Like getters, selectors encapsulate the structure of the state, and are reusable. Selectors can also compute derived properties.
You can write selectors, such as the ones you saw in redux-saga. For example:
const getUsersNumber = ({ users }) => users.length;
const getUsersIds = ({ users }) => users.map(({ id }) => id);
etc...
You can also use reselect, which is a simple “selector” library for Redux, that memoize selectors to make them more efficient.
Selectors are functions that take Redux state as an argument and return some data to pass to the component.
const getUserData = state => state.user.data;
Why should it be used?
One of the main reasons is to avoid duplicated data in Redux.
Your data object shape keeps varying as your application grows, so rather than making changes in all the related component.It is much recommended/easier to change the data at one place.
Selectors should be near reducers because they operate on the same state. It is easier for data to keep in sync.
Using reselect helps to memoize data meaning when the same input is passed to the function, returns the previous result rather than recalculating again.So, this enhances your application performance.
function mapStateToProps (state) {
return {
user: state.user,
}
}
initialState of reducer by user store
const initialState = {
isAdmin:false,
isAuth:false,
access:[1,2,5]
};
class AppComp extends React.Component{
render(){
const {user: { access:access}} = this.props;
const rand = Math.floor(Math.random()*4000)
return (<div>
{`APP ${rand} `}
<input type="button" defaultValue="change auth" onClick={this.onChangeUserAuth} />
<p>TOTAL STATUS COUNT IS {access.length}</p>
</div>)
}
}}
but you can use selector
var getUser = function(state) {
return state.user
}
const getAuthProp = createSelector(
getUser,
(user) => user.access
);
function mapStateToProps (state) {
return {
// user: state.user,
access: getAuthProp(state)
}
}
Main Problem is this component use all user: state.user and any changes in user (etc isAdmin ,isAuth, access) runs rerender this component which need only part of this store - access!!!
In Redux, whenever an action is called anywhere in the application,
all mounted & connected components call their mapStateToProps
function. This is why Reselect is awesome. It will just return the
memoized result if nothing has changed.
In the real world, you will most likely need the same certain part of
your state object in multiple components.
https://medium.com/#parkerdan/react-reselect-and-redux-b34017f8194c
The createSelector function provided by Reselect implements the most basic way to derive a selector from previous selectors. The simplest use case is to derive a selector from a single other selector. In this case, the parameters to createSelector are the input selector and a function transforming the result of that selector into the result of the new selector. For example
var getProducts = function(state) {
return state.products
}
import {getProducts} from '../app/selectors'
import {createSelector} from 'reselect'
export const getProductTitles = createSelector(
getProducts,
(products) => products.map((product) => product.get('title'))
)
This is equivalent to (ignoring memoization):
import {getProducts} from '../app/selectors'
export const getProductTitles = (state) => {
return getProducts(state).map((product) => product.get('title'))
}
The createSelector function can combine data from multiple selectors as well as from a single selector. We can pass any number of selectors to createSelector, and their results will be passed to the function passed as the final argument. For a (somewhat contrived) example:
const isInCheckout = createSelector(
getIsShippingPage,
getIsBillingPage,
getIsConfirmationPage,
(isShipping, isBilling, isConfirmation) =>
isShipping || isBilling || isConfirmation
)
is equivalent to
const isInCheckout = (state) => {
return (
getIsShippingPage(state) ||
getIsBilingPage(state) ||
getIsConfirmationPage(state)
)
}
common pattern when writing mapStateToProps functions with selectors is to return an object with each key storing the result of a particular selector. The createStructuredSelector helper function in Reselect lets us write this pattern with the minimum of boilerplate. For example, if we writ
const mapStateToProps = createStructuredSelector({
title: getProductTitle,
price: getProductPrice,
image: getProductImage
})
it is equivalent to
const mapStateToProps = (state) => {
return {
title: getProductTitle(state),
price: getProductPrice(state),
image: getProductImage(state)
}
}
https://docs.mobify.com/progressive-web/0.15.0/guides/reselect/

Resources