user1189352
user1189352

Reputation: 3875

How to prevent a whole list from re-rendering when editing an element on the list?

I created this very simple app to hopefully explain this problem.

I tried using memoization and callback, but I believe it's re-rendering because the playerArr is always changing once I type into the text input.

my actual lists are only 15 elements in size, but the re-render is causing it to become REALLY SLOW when typing into the input.

Any suggestions? I have a deadline and i'm getting stressed out =( will going back to non-hooks help? or implementing redux? not sure the performance factor.

function App() {
  const [player1, setPlayer1] = React.useState({
    firstName: "First",
    lastName: "Last ",
    id: uuidv4()
  });

  const [player2, setPlayer2] = React.useState({
    firstName: "First",
    lastName: "Last",
    id: uuidv4()
  });

  const [player3, setPlayer3] = React.useState({
    firstName: "First",
    lastName: "Last",
    id: uuidv4()
  });

  return (
    <div>
      <State
        player1={player1}
        player2={player2}
        player3={player3}
        setPlayer1={setPlayer1}
        setPlayer2={setPlayer2}
        setPlayer3={setPlayer3}
      />
    </div>
  );
}

//----------------------------------------------------------

export const State = React.memo(({player1, player2, player3, setPlayer1, setPlayer2, setPlayer3}) => {
  const playerArr = [player1, player2, player3];
  const setPlayerArr = [setPlayer1, setPlayer2, setPlayer3];

  return (
    <div>
      <Playlist
        playerArr={playerArr}
        setPlayerArr={setPlayerArr}
      />
    </div>
  );
});

//----------------------------------------------------------

export const Playlist = React.memo(({playerArr, setPlayerArr}) => {
  return (
    <div>
      {
        playerArr.map((player, index) => (
          <Player
            key={player.id}
            player={player}
            setPlayer={setPlayerArr[index]}
          />
        ))
      }
    </div>
  );
});

//----------------------------------------------------------

export const Player = React.memo(({player, setPlayer}) => {
  const handleOnChange = React.useCallback((event) => {
    const playerCopy = {...player};
    playerCopy[event.target.name] = event.target.value;
    setPlayer(playerCopy);
  }, [player, setPlayer]);

  return (
    <div>
      <input type={"text"} name={"firstName"} value={player.firstName} onChange={handleOnChange}/>
      <input type={"text"} name={"lastName"} value={player.lastName} onChange={handleOnChange}/>
    </div>
  );
});

EDIT: i edited app per discussion. same thing happening

Upvotes: 1

Views: 101

Answers (1)

jered
jered

Reputation: 11571

No matter what you do, your <App> and <Playlist> components (even if they are memoized) will HAVE to re-render every time there is a user input because that is where you are storing your state, and is to be expected.

The best you can do is memoize each <Player> component so that when the list re-renders, every individual list item doesn't necessarily re-render itself. To do this you can pass an "areEqual" function as the second argument to React.memo. See the example in the React documentation: https://reactjs.org/docs/react-api.html#reactmemo

In your case, it would probably look something like this:

export const Player = React.memo(({player, setPlayer}) => {
  const handleOnChange = React.useCallback((event) => {
    const playerCopy = {...player};
    playerCopy[event.target.name] = event.target.value;
    setPlayer(playerCopy);
  }, [player, setPlayer]);

  return (
    <div>
      <input type={"text"} name={"firstName"} value={player.firstName} onChange={handleOnChange}/>
      <input type={"text"} name={"lastName"} value={player.lastName} onChange={handleOnChange}/>
    </div>
  );
}, (prevProps, nextProps) => {
  // Check to see if the data is the same
  if (prevProps.firstName === nextProps.firstName
    && prevProps.lastName === nextProps.lastName
    && prevProps.id === nextProps.id) {
    return true; // Return true if they ARE the same
  } else {
    return false; // Return false if they are NOT the same
  }
});

Sometimes, if the data you are comparing is a simple collection of strings and/or numbers, you can use JSON.stringify as a shorthand way to convert it to a string and compare the strings:

export const Player = React.memo(({player, setPlayer}) => {
  const handleOnChange = React.useCallback((event) => {
    const playerCopy = {...player};
    playerCopy[event.target.name] = event.target.value;
    setPlayer(playerCopy);
  }, [player, setPlayer]);

  return (
    <div>
      <input type={"text"} name={"firstName"} value={player.firstName} onChange={handleOnChange}/>
      <input type={"text"} name={"lastName"} value={player.lastName} onChange={handleOnChange}/>
    </div>
  );
}, (prevProps, nextProps) => {
  // Check to see if the data is the same
  if (JSON.stringify(prevProps) === JSON.stringify(nextProps)) {
    return true; // Return true if they ARE the same
  } else {
    return false; // Return false if they are NOT the same
  }
});

Upvotes: 1

Related Questions