In my Table component I'm getting data from redux store with useSelector hook.
const info = useSelector(state => {
if (type === 'catalog') {
return store.getState().catalog.products
}
if (type === 'category') {
return store.getState().categories.categories
}
})
Then I'm processing data to correct type
React.useEffect(() => {
if(info.length) {
const prods:any = []
info.forEach((product: any) => {
const productObj: any = {}
productObj._prodid = product?._id
productObj.image = product?.catalogProduct?.image
productObj.category = product?.catalogProduct?.category.name
productObj.name = product?.catalogProduct?.name
productObj.pricePerPiece = product?.catalogProduct?.pricePerPiece
productObj.pricePerPackage = product?.catalogProduct?.pricePerPackage
productObj.address = product?.address
productObj.piecesAtStorage = product?.piecesAtStorage
prods.push(productObj)
})
setData(prods)
}
}, [info])
It takes 3 re-renders.
First rerender - initial data of useState
Second rerender - initial data from useSelector
Third rerender - set data from useSelector into useState
And the output looks like this.
Is it possible to avoid rerender caused by useSelector?
You could combine the 2 things in one selector:
const { Provider, useSelector } = ReactRedux;
const { createStore, applyMiddleware, compose } = Redux;
const { createSelector } = Reselect;
const initialState = {
catalog: {
products: [{ _id: 1 }, { _id: 2 }],
categories: [{ _id: 3 }, { _id: 4 }],
},
};
const reducer = (state) => {
return state;
};
//selectors
const selectCatalog = (state) => state.catalog;
//select product or categories and map them
const selectData = createSelector(
[selectCatalog, (_, type) => type],
(catalog, type) => {
const data =
type === 'catalog'
? catalog.products
: type === 'category'
? catalog.categories
: [];
//use map instead of forEach
return data.map((item) => ({
//SO snippet has old babel so removed optional chaining
// you can put it back in your code
_prodid: item._id,
//you can figure out the other props
}));
}
);
//creating store with redux dev tools
const composeEnhancers =
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
reducer,
initialState,
composeEnhancers(
applyMiddleware(
() => (next) => (action) => next(action)
)
)
);
const App = () => {
const [type, setType] = React.useState('catalog');
const data = useSelector((state) =>
selectData(state, type)
);
console.log('render app', type);
return (
<div>
<select
value={type}
onChange={(e) => setType(e.target.value)}
>
<option value="catalog">catalog</option>
<option value="category">category</option>
</select>
<pre>{JSON.stringify(data, undefined, 2)}</pre>
</div>
);
};
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.2.0/react-redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/reselect/4.0.0/reselect.min.js"></script>
<div id="root"></div>
Have you tried shallowEqual function?
import { shallowEqual, useSelector } from 'react-redux'
const selectedData = useSelector(selectorReturningObject, shallowEqual)
The problem is it returns a new array each time it runs. As for object and array, those with same properties/values are technically not same. This is why you are seeing same empty array twice.