Thank you for all your comments and helpful remarks. following your advices, please find below a code sandbox link
https://codesandbox.io/s/magical-butterfly-uk0fjq?file=/src/item.js
which may help you figure out the issue.
Everything is obvious in the console, with multiple logs, which, given the current scale of the example, does not pose any performance issue, but which could if there were a long list of items.
So the question is, how to proceed for preventing items which are not deleted from re-rendering, in spite of the fact that the parent (ItemList) re-renders.
The console shows the Item rendering.
As stated previously, I have used a useMemo + useCallback combination, but the result proved to be unstable.
Hope this example will help and be more explicit.
EDIT
regarding the console log, strangely, the sandbox example logs 2 times App, 2 times ItemList and 12 times Item, whereas on the computer it logs only 1-1-6 times
So, if you want to minimize the re-renders of the items, you need to make sure of a few things
You can use React.memo on the component you want, which will prevent re-renders when the props/state do not change.
so, use
export default React.memo(Item);
instead of
export default Item;
When you render the Item component you pass deleteItem as a prop with a value that is received from the App component. However, this function is not a stable (it is redefined each time the App component renders. And since the App holds the items state, it will rerender after each deletion. This will trigger a new deleteItem to be defined, and that will cause the Item to re-render.
To make this stable, you need two things.
React.useCallback which re-uses the same function when its dependencies remain the same.function form of the setItemsso, instead of
const deleteItem = (newItem) => {
const newItemList = items.filter(
(item) => item.reference !== newItem.reference
);
setItems(newItemList);
};
use
const deleteItem = React.useCallback((itemToDelete) => {
setItems((currentItems) =>
currentItems.filter((item) => item.reference !== itemToDelete.reference)
);
}, []);
You also have an issue in your code, where you .map the data but then before returning each Item you push it in an array and return that instead. Just return the <Item ..>
So instead of
{props.data.map((item, index) => {
const newList = [];
newList.push(
<Item
deleteItem={props.deleteItem}
key={item.reference}
item={item}
></Item>
);
return newList;
})}
do
{props.data.map((item, index) => {
return (
<Item
deleteItem={props.deleteItem}
key={item.reference}
item={item}
></Item>
);
})}
Updated codesandbox with all changes: https://codesandbox.io/s/twilight-water-9iyobx
It sounds like you want to iterate over your array of objects to produce rows of data in a table with the last cell in each row to be a "remove" button that removes the row. And you can do that. No need for useEffect.
const { useState } = React;
// Pass in the data
function Example({ data }) {
// Set the state with the data
const [ tableData, setTableData ] = useState(data);
// When a remove button is clicked `filter`
// out those rows that don't match the row id
// and reset the state
function handleRemove(e) {
const { id } = e.target.closest('tr').dataset;
const filtered = tableData.filter(obj => {
return obj.name !== id;
});
setTableData(filtered);
}
// Create some rows by mapping over the
// table data
return (
<table>
{tableData.map(row => {
return (
<Row
key={row.name}
row={row}
handleRemove={handleRemove}
/>
);
})}
</table>
);
}
// Create a table row and populate the cells
// with the information from the row object
function Row({ row, handleRemove }) {
return (
<tr data-id={row.name}>
<td>{row.name}</td>
<td>{row.reference}</td>
<td>{row.description}</td>
<td>
<button
onClick={handleRemove}
>Remove
</button>
</td>
</tr>
);
}
const data = [
{name: "NAME 1", reference: "REF 1", description: "LOREM IPSUM"},
{name: "NAME 2", reference: "REF 2", description: "LOREM IPSUM"},
{name: "NAME 3", reference: "REF 3", description: "LOREM IPSUM"},
{name: "NAME 4", reference: "REF 4", description: "LOREM IPSUM"},
{name: "NAME 5", reference: "REF 5", description: "LOREM IPSUM"}
];
ReactDOM.render(
<Example data={data} />,
document.getElementById('react')
);
table { border-collapse: collapse; border: 1px solid #565656; }
td { border: 1px solid #dfdfdf; padding: 0.5em;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>
When updating the state you need to spread the previous state first before you update it Example:
const deleteItem = (newItem) => {
const newItemList = items.filter(item => item.reference !== newItem.reference);
setItems(prev => (...prev, newItemList));
}