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
cardsInPlayarray to eachCard, soReact.memo()will still re-render each card because thepropshave 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 previouscardsInPlayusing the functional update signature ofsetCardsInPlay():