I have a normalized Redux store in my React Native application.
The structure of my reducer is:
{
byId: {},
allIds: []
}
In my component, I get the slice of Redux state using the useSelector hook:
const categories = useSelector((state: AppState) =>
state.products.allIds.map((id) => state.categories.byId[id.toString()])
);
The logic in the useSelector just converts the byId object into an array.
The infinite looping occurs when I set the categories array as a dependency:
const [values, setValues] = useState<any[]>([]);
useEffect(() => {
setValues([{ id: 1 }]);
console.log("logging");
}, [categories]);
Not sure what is the problem. I believe it's the useSelector logic that converts the objects into an array.
EDIT:
Full component code:
// React
import React, { useEffect, useState } from "react";
// React redux
import { useSelector } from "react-redux";
import { AppState } from "@reducers/rootReducer";
// Logic
import ProductsScreenLogic from "./ProductsScreen.logic";
// Common components
import ScreenView from "@common/screen/Screen.view";
// Components
import NewProductModalView from "@components/products/new-product-modal/NewProductModal.view";
import ProductsTabsView from "@components/products/products-tabs/ProductsTabs.view";
import ProductListView from "@components/products/products-list/ProductList.view";
import CategoryListView from "@components/products/category-list/CategoryList.view";
const ProductsScreenView: React.FC = () => {
const { displayProductList, setDisplayProductList, products } =
ProductsScreenLogic();
// Makes the categories ById object into an array of categories
const categories = useSelector((state: AppState) => state.categories.allIds.map((id) => state.categories.byId[id.toString()])
);
const [values, setValues] = useState<any[]>([]);
useEffect(() => {
setValues([{ id: 1 }]);
console.log("logging");
}, [categories]);
return (
<>
<NewProductModalView />
<ScreenView></ScreenView>
</>
);
};
export default ProductsScreenView;
The problem is that your selector always returns a new reference (because of the call to
map). You could instead usecreateSelectorwhich will memoize it and only return a new reference when something inside eitherallIdsorbyIdchanges:But ideally you should avoid such selectors that go through the whole
byIdobject, because it kind of negates the benefits of having a normalized state. You should rather have a parent component that only selectsstate.categories.allIds, and then passes the ids as props to child components, and every child component will select its ownstate.categories.byId[id]. This way if a category changes, only the corresponding child component will rerender, instead of having the parent and all of the children rerender.