Idea Post about Redux basics To make a reactive variable from the store we need to do ... const counter = useSelector(state => state.counter) state => state.counter this function is called selector and returns part of the sate Selector can be extracted into a separate selector function, can be used in case the same selector is used in multiple places With createSelector we can pass several selectors into it and return new modified cached selector Also good explanation how selectors work Official explanation how comparison in selectors work Installation npm i reselect
import { combineReducers } from 'redux';
import { Provider, useSelector, useDispatch } from 'react-redux'
import { createSelector } from 'reselect'
import { configureStore } from '@reduxjs/toolkit'
const initApples = 5
const apples = (state = initApples, action) => {
if (action.type === 'ADD APPLE') return state + (action.num || 1)
return state
}
const initBananas = 10
const bananas = (state = initBananas, action) => {
if (action.type === 'ADD BANANA') return state + (action.num || 1)
return state
}
const rootReducer = combineReducers({
apples,
bananas,
})
const store = configureStore({
reducer: rootReducer,
devTools: true,
})
const getApples = state => state.apples
const getBananas = state => state.bananas
const getTotal = createSelector(
[getApples, getBananas],
(applesQty, bananasQty) => applesQty + bananasQty
)
<Provider store={store}>
<Component />
</Provider>
What's going on here? ... We've split the function into standalone selectors to get one piece of data Then, we combine the fragments with the createSelector() function It takes the fragments and a transform function as the last argument That transform function receives the results from the fragments Whatever it returns is what gets returned by the “master” selector createSelector() returns the “master” selector which can take state and optionally props It passes ( state , props ) to each of the fragment selectors resultEqualityCheck we can control how selector compare if state has been changed in my example i was interested if we added or removed any items from the array and if an item id was changed but was not interested if any property value inside of an item was changed or not to do that I mapped through an array and returned same array but only with id props but it did not help, because map method every time returned a new array, even it looks the same and re-renders happened all the time with resultEqualityCheck we manually can control how we check updated and previous state
export const selectItemsShape = createSelector(
[(state: RootState) => state.items],
(items) => items,
{
memoizeOptions: {
resultEqualityCheck: (prevItems:ItemsType, currentItems:ItemsType) => {
const addedOrDeletedItem = prevItems.length !== currentItems.length
if (addedOrDeletedItem) return false
const itemsIdsDoNotMatch = prevItems.some((item, index) => item.id !== currentItems[index]?.id)
if (itemsIdsDoNotMatch) return false
return true
}
}
}
)
export const Items = () => {
const itemsShape = useSelectorTyped(selectItemsShape)
return (
<ItemsContainer>
{itemsShape.map((item, index) => {
if (item.type === 'text') return <TextItem key={item.id} item={item} index={index} />
if (item.type === 'text editable') return <FroalaItem key={item.id} item={item} index={index} />
if (item.type === 'paste') return <PasteText key={item.id} />
return null
})}
</ItemsContainer>
)
}
Pass an argument https://stackoverflow.com/a/61220891/7239778
// selector.js
const selectItemsByCategory = createSelector(
[
state => state.items,
(state, category) => category
],
(items, category) => items.filter(item => item.category === category)
);
// App.js
const items = selectItemsByCategory(state, 'javascript');
// Another way if you're using redux hook:
const items = useSelector(state => selectItemsByCategory(state, 'javascript'));