React-redux rerenders when creating arrays - reactjs

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.

Related

redux useSelector is not giving root state

I'm working on a project that use Redux toolkit in typescript language.
The problem is that when I use createSelector it returns wrong state object in some of the selectors, not all of them. And it will work, if I use store.getState() instead of returned state. Let me describe more details.
My state includes one reducer which includes two reducers that combined using combineReducers, as shows below.
const entities = combineReducers({
trello: entityTrello,
users: entityUser,
})
export function makeStore() {
return configureStore({
reducer: {
entities: entities,
},
devTools: process.env.NEXT_PUBLIC_PROJECT_STATUS == "develop",
})
}
trello reducer contains boards and their cards.
users reducer contains list of users.
all things go right and after saving data in store I got below state tree
Then I try to get data from state using createSelector.
export const selectLanesByBoardId = () => {
return useAppSelector<BoardData>(selector => createSelector(
(state: AppState) => state.entities.trello.tables.data?.boards,
(state: AppState) => state.entities.trello.tables.data?.lanes,
(state: AppState) => state.entities.trello.tables.data?.cards,
(state: AppState) => state.entities.trello.meta.boardId,
(state: AppState) => state.entities.trello.meta.workspace,
(boards, lists, cards, boardId, workspace) => {
let data: Array<Lane>
// because we have key-value array, first we change it to simple array using "objectValues"
// then find lanes of current board by "boardId" which sent from component (it's ids of lanes, not whole object)
const laneIds = objectValues<TrelloBoard>(boards)?.find(value => value.id == boardId)?.lists
// now with using "laneIds" search for specific lane objects
const lanes = objectValues<TrelloLanes>(lists)?.filter(value => laneIds?.includes(value.id))
// any lane has it's own cards that not implemented yet, so update cards field using "map" on array of lanes
data = lanes.map(lane => ({
...lane,
cards: objectValues<TrelloCard>(cards)
?.filter(value => lane?.cards?.includes(value.id))
.map(value => ({
...value,
draggable: workspace == "free",
laneId: lane.id
}))
}))
return {lanes: data} as BoardData
}
)(selector))
}
It goes right again, and I got correct data.
But in another createSelector the returned state is not the root state. please check below code.
export const selectUsers = () => {
return useAppSelector<Array<User>>(selector => createSelector(
(state: AppState) => {
return state.entities.users.tables.data?.users
},
(users) => {
return users ?? []
}
)(selector))
}
In this part of code I get an error which shows below.
As I said, it will work if I use store.getState() instead of state. Here is the code
export const selectUsers = () => {
return useAppSelector<Array<User>>(selector => createSelector(
(state: AppState) => {
return store.getState().entities.users.tables.data?.users
},
(users) => {
return users ?? []
}
)(selector))
}
Last, my question is why createSelector is not returning the root state in some part of the code, but store.getState() does. Any help, please?
update
As the shared image of the error shows, it says that entities object is undefined, not users. So I figured out that the root state is changing as #GabrielePetrioli noted in a comment, by adding a new console.log in createSelector of users, the state shows below data in the console log.
it's showing a new object instead of my store structure.
But how may the root state entity changes? I even has no access to entities object in my reducers.
below code is one of updating samples of the state.
state.tables.data && (state.tables.data = {
...state.tables.data,
lanes: state.utils.nextStateAfterLastAction?.lanes ?? [],
cards: state.utils.nextStateAfterLastAction?.cards ?? [],
})
as you can see, I just have access to the tables object, not the entities object.
every reducer updates its own state, not the root structure of the state!

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

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.

Redux useSelector with id field

I need your advice on filtering data with a selector. Suppose I have the following entities in my application. 1 organization has multiple devices which look the following in my state shape:
state {
devices: {
byId: [ 1 { name: device1 } ]
}
organizations: {
byId: [
1 { name: org1, devices: [1,2,3] }
]
}
}
Now I want to filter the devices inside the organization. This is something that I want to do with a selector. My selector looks like the following:
const selectDevice = (id: any) => (state: State) => state.devices.byId[id]
export const selectOrganizationDevices = (id: number) => (state: State) => {
const organization = state.organizations.byId[id] || []
return organization.devices.map(id => selectDevice(id)(state))
}
This should be working fine but my selector got called before I have dispatched the data from redux. I suppose that there's something wrong with my reducer or the component I've created.
My reducer looks like this:
return produce(state, draftState => {
switch (action.type) {
...
case OrganizationActionTypes.FETCH_DEVICES_SUCCESS: {
draftState.byId = action.payload.entities.organizations
draftState.allIds = Object.keys(action.payload.entities.organizations)
draftState.loading = false
break;
}
...
default: {
return state
}
}
})
My functional component looks like the following:
function Devices() {
const dispatch = useDispatch();
const devices = useSelector(selectOrganizationDevices(1))
useEffect(() => {
dispatch(fetchOrganizationDevices(1))
}, [])
const columns = []
return (
<Layout headerName={"Devices"}>
<Table dataSource={devices} columns={columns}/>
</Layout>
)
}
The error I get now is organization.devices is undefined which says that the array of devices in the state is empty. It seems that useSelector is called before dispatch. How can I prevent redux of doing this? Or what should be changed in my code?
Yes, the useEffect hook runs after the first render. useSelector will run during the first render. So, your component code needs to safely handle the case where that data doesn't exist yet.
Also, don't put a hardcoded array/object literal in a selector like that, as it will be a new reference every time and force your component to re-render every time an action is dispatched. Either extract that to a constant outside of the selector, or use a memoization library like Reselect to create the selector.
Finally, you should be using our official Redux Toolkit package, which includes utilities to simplify several common Redux use cases, including store setup, defining reducers, immutable update logic, and even creating entire "slices" of state at once. It also has a new createEntityAdapter API that helps you manage normalized state in the store.

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!

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