简体   繁体   English

React Memo 重置组件 state 中的值

[英]React Memo resets values in component state

What I am trying to do我想要做什么

  1. I'm trying to use an array of objects (habits) and render a "Card" component from each one.我正在尝试使用一组对象(习惯)并从每个对象中渲染一个“卡片”组件。
  2. Now for each Card (habit) you can "mark" that habit as done, which will update that Card's state.现在,对于每张卡片(习惯),您可以将该习惯“标记”为已完成,这将更新该卡片的 state。
  3. I've used React.memo to prevent other Cards from re-rendering.我使用 React.memo 来防止其他卡片重新渲染。
  4. Whenever the user mark a card as done, the card header gets changed to "Edited"每当用户将卡片标记为完成时,卡片 header 就会更改为“已编辑”

The Issue I'm facing我面临的问题

Whenever a card is marked as done, the header changes alright, but whenever any other card is marked as done, the first card's state gets reverted back.每当一张卡被标记为完成时,header 就会正常更改,但只要任何其他卡被标记为完成,第一张卡的 state 就会恢复原状。

I've not been able to find other people facing a similar issue, can somebody please help?我找不到其他面临类似问题的人,有人可以帮忙吗?

Here is the code:这是代码:

import React, { useState } from "react";

const initialState = {
  habits: [
    {
      id: "1615649099565",
      name: "Reading",
      description: "",
      startDate: "2021-03-13",
      doneTasksOn: ["2021-03-13"]
    },
    {
      id: "1615649107911",
      name: "Workout",
      description: "",
      startDate: "2021-03-13",
      doneTasksOn: ["2021-03-14"]
    },
    {
      id: "1615649401885",
      name: "Swimming",
      description: "",
      startDate: "2021-03-13",
      doneTasksOn: []
    },
    {
      id: "1615702630514",
      name: "Arts",
      description: "",
      startDate: "2021-03-14",
      doneTasksOn: ["2021-03-14"]
    }
  ]
};

export default function App() {
  const [habits, setHabits] = useState(initialState.habits);

  const markHabitDone = (id) => {
    let newHabits = [...habits];
    let habitToEditIdx = undefined;

    for (let i = 0; i < newHabits.length; i++) {
      if (newHabits[i].id === id) {
        habitToEditIdx = i;
        break;
      }
    }

    let habit = { ...habits[habitToEditIdx], doneTasksOn: [], name: "Edited" };
    newHabits[habitToEditIdx] = habit;
    setHabits(newHabits);
  };

  return (
    <div className="App">
      <section className="test-habit-cards-container">
        {habits.map((habit) => {
          return (
            <MemoizedCard
              markHabitDone={markHabitDone}
              key={habit.id}
              {...habit}
            />
          );
        })}
      </section>
    </div>
  );
}

const Card = ({
  id,
  name,
  description,
  startDate,
  doneTasksOn,
  markHabitDone
}) => {
  console.log(`Rendering ${name}`);
  return (
    <section className="test-card">
      <h2>{name}</h2>
      <small>{description}</small>
      <h3>{startDate}</h3>
      <small>{doneTasksOn}</small>
      <div>
        <button onClick={() => markHabitDone(id, name)}>Mark Done</button>
      </div>
    </section>
  );
};

const areCardEqual = (prevProps, nextProps) => {
  const matched =
    prevProps.id === nextProps.id &&
    prevProps.doneTasksOn === nextProps.doneTasksOn;

  return matched;
};

const MemoizedCard = React.memo(Card, areCardEqual);

Note: This works fine without using React.memo() wrapping on the Card component.注意:这在不使用 React.memo() 包装 Card 组件的情况下可以正常工作。

Here is the codesandbox link: https://codesandbox.io/s/winter-water-c2592?file=/src/App.js这是代码框链接: https://codesandbox.io/s/winter-water-c2592?file=/src/App.js

Problem is because of your (custom) memoization markHabitDone becomes a stale closure in some components.问题是因为您的(自定义)记忆markHabitDone在某些组件中变成了一个陈旧的闭包。

Notice how you pass markHabitDone to components.请注意如何将markHabitDone传递给组件。 Now imagine you click one of the cards and mark it as done.现在假设您单击其中一张卡片并将其标记为完成。 Because of your custom memoization function other cards won't be rerendered, hence they will still have an instance of markHabitDone from a previous render .由于您的自定义记忆 function其他卡片不会被重新渲染,因此它们仍然会有一个来自先前渲染markHabitDone实例。 So when you update an item in a new card now:因此,当您现在更新新卡中的项目时:

let newHabits = [...habits];

the ...habits there is from previous render .来自先前渲染...habits So the old items are basically re created this way.所以旧的物品基本上都是这样重新创建的。

Using custom functions for comparison in memo like your areCardEqual function can be tricky exactly because you may forget to compare some props and be left with stale closures.memo中使用自定义函数进行比较,例如areCardEqual function 可能会很棘手,因为您可能会忘记比较一些道具并留下陈旧的闭包。

One of the solutions is to get rid of the custom comparison function in memo and look into using useCallback for the markHabitDone function.解决方案之一是在memo中去掉自定义比较 function 并考虑使用useCallback来表示markHabitDone function。 If you also use [] for the useCallback then you must rewrite markHabitDone function (using the functional form of setState ) such that it doesn't read the habits using a closure like you have in first line of that function (otherwise it will always read old value of habits due to empty array in useCallback ).如果您还使用[]作为useCallback那么您必须重写markHabitDone function (使用setState函数形式),这样它就不会像在 function 的第一行中那样使用闭包来读取habits (否则它将始终读取由于useCallback中的空数组而导致habits的旧值)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM