最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - In React, how to avoid unnecessary rerender when array used for rendering changes? - Stack Overflow

programmeradmin4浏览0评论

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
Add a ment  | 

3 Answers 3

Reset to default 4

That'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 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.

    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>
    </>
  );

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论