skip to Main Content

I have an array of card component. I will often be adding a card on a user action(like click a button) or the user is able to remove a card. However, since it’s useState when the state changes it gets re rendered. But In the case I have 3 cards in my array and that I add a 4th one I really don’t want to re render the other 3 when no change happened to them but it’s just that they are being in an array made from useState.

The requirement is that it doesn’t re render existing component whether I add or remove an element from the array.

I’ve tried useState & useRef and custom hook and no luck there. With useRef it didn’t re render when I needed it to re render. Like it didn’t re render the existing but also didn’t re render to show the new one. The custom hook combine the add and remove feature but still used useState from within.

Here is a smaller version of the issue in a sandbox. For the sake of a quick example, I’m hardcoding the remove function.
In the console, you’ll see the console log printing when you add or remove and that’s inside the card component(shouldn’t happen ideally)
https://codesandbox.io/s/no-rerender-array-element-jvu6q5

Thanks for any help!

import "./styles.css";
import React, { useEffect, useRef, useState, useContext } from "react";

const fakeData1 = {
  Card1: [1, 2, 3, 4]
};
const fakeData2 = {
  Card2: [5, 6, 7, 8]
};

const fakeObject = { fakeData1 };
export default function App() {
  const [cardArray, setCardArray] = useState(fakeObject);

  const addCard = () => {
    setCardArray((entityState) => ({
      ...entityState,
      fakeData2
    }));
  };

  const Card = ({ id, index, item }) => {
    console.log("Rendering Card: ", item);
    const handleRemove = (event: any | MouseEvent) => {
      if (event.type == "click" || event.type == "keydown") {
        setCardArray((entityState) => {
          const updatedData: any = { ...entityState };
          delete updatedData["fakeData2"];
          return updatedData;
        });
      }
    };
    return (
      <div style={{ border: "black solid 2px", padding: "50px 0" }}>
        <h1>Card - {id}</h1>
        <div>Content: {Object.values(item)}</div>
        <button onClick={handleRemove}>Remove</button>
      </div>
    );
  };

  return (
    <div className="App">
      <button onClick={addCard}>Add a Card</button>
      {Object.values(cardArray)
        .flat()
        .map((item: any, index) => {
          return <Card id={index} key={index} item={item} />;
        })}
    </div>
  );
}


2

Answers


  1. you can use React.memo to prevent components from re-rendering without any updates.like this:

    import "./styles.css";
    import React, { useEffect, useRef, useState, useContext, memo } from "react";
    
    const fakeData1 = {
      Card1: [1, 2, 3, 4]
    };
    const fakeData2 = {
      Card2: [5, 6, 7, 8]
    };
    
    const Card = memo(({ id, index, item, setCardArray }) => {
      console.log("Rendering Card: ", item);
      const handleRemove = (event: any | MouseEvent) => {
        if (event.type == "click" || event.type == "keydown") {
          setCardArray((entityState) => {
            const updatedData: any = { ...entityState };
            delete updatedData["fakeData2"];
            return updatedData;
          });
        }
      };
      return (
        <div style={{ border: "black solid 2px", padding: "50px 0" }}>
          <h1>Card - {id}</h1>
          <div>Content: {Object.values(item)}</div>
          <button onClick={handleRemove}>Remove</button>
        </div>
      );
    });
    
    
    const fakeObject = { fakeData1 };
    export default function App() {
      const [cardArray, setCardArray] = useState(fakeObject);
    
      const addCard = () => {
        setCardArray((entityState) => ({
          ...entityState,
          fakeData2
        }));
      };
    
      
      return (
        <div className="App">
          <button onClick={addCard}>Add a Card</button>
          {Object.values(cardArray)
            .flat()
            .map((item: any, index) => {
              return <Card setCardArray={setCardArray} id={index} key={index} item={item} />;
            })}
        </div>
      );
    }
    
    

    And i suggest that the key of the component should be set to a unique value, so that the component can be executed in the way we expected.
    Hope it helps you.

    Login or Signup to reply.
  2. What you need is to memo-ize your state and functions, you can use React.memo memoize (make it so that it doesn’t refresh unecesarly) and useCallback to memo-ize the removeCard function, you also need to move the removeCard funct to outside of the card component and pass it as a prop as every time a new card is made it creates a new removeCard function causing a re-render you can update your code like so e(Im based it on the codeSandbox):

    import "./styles.css";
    import React, {
      useEffect,
      useRef,
      useState,
      useContext,
      useCallback
    } from "react";
    
    const fakeData1 = {
      Card1: [1, 2, 3, 4]
    };
    const fakeData2 = {
      Card2: [5, 6, 7, 8]
    };
    const Card = React.memo(({ id, item, onRemove }) => {
      console.log("Rendering Card: ", item);
    
      return (
        <div style={{ border: "black solid 2px", padding: "50px 0" }}>
          <h1>Card - {id}</h1>
          <div>Content: {Object.values(item)}</div>
          <button onClick={onRemove}>Remove</button>
        </div>
      );
    });
    
    const fakeObject = { fakeData1 };
    export default function App() {
      const [cardArray, setCardArray] = useState(fakeObject);
    
      const addCard = () => {
        setCardArray((entityState) => ({
          ...entityState,
          fakeData2
        }));
      };
    
    
      const removeCard = useCallback(() => {
        setCardArray((entityState) => {
          const updatedData = { ...entityState };
          delete updatedData["fakeData2"];
          return updatedData;
        });
      }, []);
    
      return (
        <div className="App">
          <button onClick={addCard}>Add a Card</button>
          {Object.values(cardArray)
            .flat()
            .map((item, index) => {
              const cardName = `Card${index + 1}`;
              return (
                <Card
                  id={index}
                  key={index}
                  item={item}
                  removeCard={removeCard}
                />
              );
            })}
        </div>
      );
    
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search