In similar question, the solution is to use an unique value instead of index of map as the 'key' prop when looping through the array to render ponents. However, it not working for me as the remaning ponents still rerender after i delete an element from the array. What am i missing here?
Here is my attempts,which is not working as expected:
App.js
import { useState } from "react";
import Column from "./Column.js";
export default function App() {
const data = [
{
name: "lebron",
age: 36,
team: "lakers"
},
{
name: "chris",
age: 36,
team: "suns"
},
{
name: "james",
age: 32,
team: "nets"
}
];
const [players, setPlayers] = useState(data);
const handleClick = () => {
return function () {
const p = [...players];
p.splice(0, 1);
setPlayers(p);
};
};
return (
<>
<table>
<tr>
<td>name</td>
<td>age</td>
<td>team</td>
</tr>
{players.map((p) => (
<Column key={p.name} player={p} onClick={handleClick(p.name)} />
))}
</table>
<button onClick={handleClick()}>Delete</button>
</>
);
}
Column.js:
import React from "react";
export default function Column({ player, onClick }) {
console.log(player.name, "update", Date());
return (
<tr>
<td>{player.name}</td>
<td>{player.age}</td>
<td>{player.team}</td>
</tr>
);
}
In similar question, the solution is to use an unique value instead of index of map as the 'key' prop when looping through the array to render ponents. However, it not working for me as the remaning ponents still rerender after i delete an element from the array. What am i missing here?
Here is my attempts,which is not working as expected:
App.js
import { useState } from "react";
import Column from "./Column.js";
export default function App() {
const data = [
{
name: "lebron",
age: 36,
team: "lakers"
},
{
name: "chris",
age: 36,
team: "suns"
},
{
name: "james",
age: 32,
team: "nets"
}
];
const [players, setPlayers] = useState(data);
const handleClick = () => {
return function () {
const p = [...players];
p.splice(0, 1);
setPlayers(p);
};
};
return (
<>
<table>
<tr>
<td>name</td>
<td>age</td>
<td>team</td>
</tr>
{players.map((p) => (
<Column key={p.name} player={p} onClick={handleClick(p.name)} />
))}
</table>
<button onClick={handleClick()}>Delete</button>
</>
);
}
Column.js:
import React from "react";
export default function Column({ player, onClick }) {
console.log(player.name, "update", Date());
return (
<tr>
<td>{player.name}</td>
<td>{player.age}</td>
<td>{player.team}</td>
</tr>
);
}
Share
Improve this question
asked Jun 13, 2021 at 3:13
Nick0712Nick0712
332 silver badges7 bronze badges
3
- 2 You are changing the state of your ponent which causes a re-render. The only think I would say would be to move your data array outside of your ponent so it doesn't get defined each re-render – Richard Hpa Commented Jun 13, 2021 at 3:16
- 2 You need to memoize the Column ponent. es.reactjs/docs/react-api.html but you need to have some react performance background. – Nacho Zullo Commented Jun 13, 2021 at 3:23
- 1 I have to say that your handleClick function is not right. When you use splice you are modifyng the players array and in React state must be modified only using the dispatch function (setPlayers). In your case, you should use .filter method because it returns a new Array. – Nacho Zullo Commented Jun 13, 2021 at 3:28
3 Answers
Reset to default 4That's the default React behavior.
The children updates because an update on the father state occurred.
If you want to re-render child ponents only if the props has changed you need to wrap your child ponent with React.memo HOC.
Briefly, with React.memo
React renders your ponent the first time, memoize the result and then reuse the last rendered result (skipping unnecessary re-renders).
React.memo only checks for prop changes. If your function ponent wrapped in React.memo
has a useState
, useReducer
or useContext
Hook in its implementation, it will still re-render when state or context change.
In your case:
Column.js file
import { memo } from "react";
function Column({ player, onClick }) {
console.log(`Render player ${player.name}`);
return (
<tr>
<td>{player.name}</td>
<td>{player.age}</td>
<td>{player.team}</td>
<button onClick={() => onClick(player)}>Delete</button>
</tr>
);
}
export default memo(Column);
App.js file
import { useState, useCallback } from "react";
import Column from "./Column";
const data = [
{
name: "lebron",
age: 36,
team: "lakers"
},
{
name: "chris",
age: 36,
team: "suns"
},
{
name: "james",
age: 32,
team: "nets"
}
];
export default function App() {
const [players, setPlayers] = useState(data);
const handleClick = useCallback((deleted) => {
setPlayers((prevState) =>
prevState.filter((player) => player.name !== deleted.name)
);
}, []);
return (
<>
<table>
<tr>
<td>name</td>
<td>age</td>
<td>team</td>
</tr>
{players.map((p) => (
<Column key={p.name} player={p} onClick={handleClick} />
))}
</table>
{/* This button makes no sense */}
<button onClick={() => handleClick(players[0])}>Delete</button>
</>
);
}
Code sandbox
I make a few changes in your code:
handleClick
function is not right. When you usesplice
you are modifyng the players array and in React state must be modified only using the dispatch function (setPlayers). In your case, you should use filter method because it returns a new Array.const handleClick = (deleted) => { setPlayers((prevState) => prevState.filter((player) => player.name !== deleted.name) ); };
wrap your
Column
ponent with React.memo HOC.
export default React.memo(Column);
wrap your
handleClick
function with useCallback. This is because you need to memoize your function due to you are using React.memo.const handleClick = useCallback((deleted) => { setPlayers((prevState) => prevState.filter((player) => player.name !== deleted.name) ); }, []);
Hope this solves your problem
import { useState } from "react";
function Column({ player, onClick }) {
console.log(player.name, "update", Date());
return (
<tr>
<td>{player.name}</td>
<td>{player.age}</td>
<td>{player.team}</td>
<td onClick={onClick}>x</td>
</tr>
);
}
export default function App() {
const data = [
{
name: "lebron",
age: 36,
team: "lakers"
},
{
name: "chris",
age: 36,
team: "suns"
},
{
name: "james",
age: 32,
team: "nets"
}
];
const [players, setPlayers] = useState(data);
const handleClick = (index) => {
return function () {
if(index===-1)
return setPlayers([])
const p = [...players];
p.splice(index, 1);
setPlayers(p);
};
};
return (
<>
<table>
<tr>
<td>name</td>
<td>age</td>
<td>team</td>
</tr>
{players.map((p, index) => (
<Column key={p.name} player={p} onClick={handleClick(index)} />
))}
</table>
<p> Click x to delete</p>
<button onClick={handleClick(0)}>Delete First</button>
<button onClick={handleClick(players.length-1)}>Delete Last</button>
<button onClick={handleClick(-1)}>Delete All</button>
</>
);
}
https://codesandbox.io/s/objective-sky-77bwz?file=/src/App.js
I will assume that you want to delete the last element of your Array for that there is the pop() method
- And not call directly function in onClick
- Also use thead and tbody in your table
const handleClick = () => {
const p = [...players];
p.pop();
setPlayers(p);
};
return (
<>
<table>
<thead>
<tr>
<td>name</td>
<td>age</td>
<td>team</td>
</tr>
</thead>
<tbody>
{players.map((p) => (
<Column
key={p.name}
player={p}
onClick={() => handleClick(p.name)}
/>
))}
</tbody>
</table>
<button onClick={handleClick}>Delete</button>
</>
);