I have a PlayArea component with a number of Card components as children, for a card game.
The position of the cards is managed by the PlayArea, which has a state value called cardsInPlay
, which is an array of CardData
objects including positional coordinates among other things. PlayArea passes cardsInPlay
and setCardsInPlay
(from useState
) into each Card child component.
Cards are draggable, and while being dragged they call setCardsInPlay
to update their own position.
The result, of course, is that cardsInPlay
changes and therefore every card re-renders. This may grow costly if a hundred cards make it out onto the table.
How can I avoid this? Both PlayArea and Card are functional components.
Here's a simple code representation of that description:
const PlayArea = () => {
const [cardsInPlay, setCardsInPlay] = useState([]);
return (
<>
{ cardsInPlay.map(card => (
<Card
key={card.id}
card={card}
cardsInPlay={cardsInPlay}
setCardsInPlay={setCardsInPlay} />
}
</>
);
}
const Card = React.memo({card, cardsInPlay, setCardsInPlay}) => {
const onDrag = (moveEvent) => {
setCardsInPlay(
cardsInPlay.map(cardInPlay => {
if (cardInPlay.id === card.id) {
return {
...cardInPlay,
x: moveEvent.clientX,
y: moveEvent.clientY
};
}
return cardInPlay;
}));
};
return (<div onDrag={onDrag} />);
});
The problem is you're passing the entire
cardsInPlay
array to eachCard
, soReact.memo()
will still re-render each card because theprops
have changed. Only pass the element that each card needs to know about and it will only re-render the card that has changed. You can access the previouscardsInPlay
using the functional update signature ofsetCardsInPlay()
: