I am building a small framework for e-commerce or blog app using React
.
One of the features of this framework is an "Add to Favourite List" function.
The app tree works as follow :
App
file stores the selectedItem
chosen by the Menu
, and then App
dispatches this selectedItem
to a displayCategoryItems
, which is the central view of the app.
The data is managed by the state provided by the useHistory
hook, so I'm not using a classical props
data management.
Once data ID is passed on to displayCategoryItems
, it's being fetched on Firestore
using an async
function.
Data retrieved is passed to a List
component which loops through the Firestore
object and makes the data readable, and creates as much Item
subcomponents as necessary in the display area using forEach
.
Each Item
has a function inherited from App
which allows to add this Item
to the favs (favourite) array state in App
.
There might be a performance issue, though :
Each time an Item
is being added to the favs
state, it causes rerenders of displayCategoryItems
and List
, and also to all the Item
in it, which can be problematic since the list of Item
can be potentially huge.
I thought about wrapping the handleFavs
function in a useCallback
, but if I do so, the function loses its ability to check whether the Item
name is already in the favs, which is not good, since it allows to add the item multiple times, and it does not prevent the multiple rerenders of Item
anyway.
Here's the code :
App
import React, { useCallback, useEffect, useState} from "react";
import Menu from "./menu";
import "./main.css";
import MenuData from "./menuData";
import { BrowserRouter as Router, Switch, Route} from 'react-router-dom';
import Footer from "./footer";
import Header from "./header";
import DisplayCategoryItems from "./displayCategoryItems";
function App() {
const [selectedItem, setMenuItem] = useState(null)
const [favs, setFavs] = useState([])
const handleMenuItem = useCallback((menuItem) => {
setMenuItem(menuItem)
}, [])
const handleFavs = (newFav) => {
if (favs.indexOf(newFav) > -1) {
return;
} else {
setFavs(previousFavs => [...previousFavs, newFav])
}
}
return (
<div className="App">
<Router>
<Header
handleMenuItem={handleMenuItem}
></Header>
<Menu
// data for menu comes in an object stored locally : MenuData
items={MenuData}
handleMenuItem={handleMenuItem}
// for giving CSS style to the selected menuItem
selectedItem={selectedItem}
></Menu>
<div id="wrapper">
<Switch>
<Route path = "/menu/:categoryItem/" exact render = {() =>
<DisplayCategoryItems
handleFavs={handleFavs}
></DisplayCategoryItems>}>
</Route>
</Switch>
</div>
</Router>
<Footer></Footer>
</div>
);
}
export default App;
displayCategoryItems child of App
import React, { useEffect, useState} from "react";
import { collection, getDocs, where, query } from 'firebase/firestore'
import { db } from "./firebase-config";
import { useHistory} from "react-router-dom";
import Liste from "./liste";
const DisplayCategoryItems = (props) => {
const [categoryItemData, setCategoryData] = useState([])
const history = useHistory()
const getCategoryData = async () => {
const queryCategories = collection(db, "items")
let data = query(queryCategories, where("category", "==", history.location.state.entry))
const querySnapshot = await getDocs(data);
setCategoryData(querySnapshot)
}
useEffect(() => {
getCategoryData()
}, [])
return (
<>
<div
id="displayCategoryItems"
className="display"
>
<Liste
data={categoryItemData}
handleFavs={props.handleFavs}
></Liste>
</div>
{console.log("Display Category Items")}
</>
)
};
export default DisplayCategoryItems;
List child of DisplayCategoryItems
import React from "react";
import Item from "./item";
const Liste = (props) => {
const categoryItemDataList=[]
props.data.forEach(doc => {
// items can have several different packaging, hence this For loop
for (let i = 0; i < doc.data().packaging.length; i++) {
categoryItemDataList.push(
<Item
key={doc.data().name + i}
data={doc.data()}
handleFavs={props.handleFavs}
></Item >
)
}
})
return (
<> <div>
{categoryItemDataList}
</div>
{console.log("Liste")}
</>
)
};
export default Liste;
Item component for List
import React from "react";
const Item = (props) => {
return (
<>
<div
className="item"
>
<div
className="itemBody"
>{props.data.name}</div>
<table className="ItemFooter">
<tbody>
<tr>
///////// favs are added here ////////////////
<th className="ItemddToFav" onClick={() => props.handleFavs(props.data.name)}></th>
<th className="ItemBuy">Buy</th>
<th className="ItemRight"></th>
</tr>
</tbody>
</table>
</div>
{console.log("Item")}
</>
)
}
export default Item;
And the other tree of the project, the Menu
Menu child of App
import React from "react";
import MenuItem from "./menuItem";
const Menu = (props) => {
const menuItemList = []
for (const [entry, value] of Object.entries(props.items)) {
menuItemList.push(
<MenuItem
key={entry}
entry={entry}
handleMenuItem={props.handleMenuItem}
value={value.value}
classN={props.selectedItem === entry ? "menuItem selected" : "menuItem"}
></MenuItem>
)
}
return (
<> <div id="menuWrapper">
<table id="menu">
<tbody>
<tr>
{menuItemList}
</tr>
</tbody>
</table>
</div>
{console.log("menu")}
</>
)
};
export default React.memo(Menu);
MenuItem component for Menu
import React from "react";
import { useHistory} from "react-router-dom";
const MenuItem = (props) => {
const history = useHistory()
const handleClick = () => {
props.handleMenuItem(props.entry)
history.push(`/menu/${props.entry}`)
history.replace({
state : props.entry
})
}
return (
<>
<th
onClick={() => handleClick()}
className={props.classN}
>{props.value}</th>
{console.log("MenuItem's render")}
</>
)
};
export default React.memo(MenuItem);
Anyone has an idea on how to stop these multiple rerendering while managing the favs
state properly ?