从子组件更新状态

时间:2021-01-15 10:25:25

标签: reactjs

看看应用程序。有一个按钮列表。我真的很难向你们解释这个问题,所以你自己看看这个应用程序,把这当作一个挑战。这确实是一个小应用程序,用于演示我在使用 react 时遇到的问题。这不是基本的东西...

https://codesandbox.io/s/cranky-faraday-bxufk?file=/src/App.js

当点击按钮时,它旁边的布尔值预计会变为 false。乍一看,这对您来说似乎很基础,但是当您查看代码时,这并不容易实现。我的实际应用程序更复杂,所以我制作了一个简单的版本来演示这个问题

Expect:
Click on button 1 => boolean next to it turns to false
Click on button 2 => boolean next to it turns to false
Click on button 3 => boolean next to it turns to false

Reality:
Click on button 1 => screen goes blank

2 个答案:

答案 0 :(得分:0)

您代码中的问题与状态更新有关。手柄按钮需要处理状态为:

const handleButton = (id) => {
    console.log("On button click: ", id);

    setState((state) => {
      // Update isSaved of a corresponding button to false
      const updatedState = [...state];
      const updatedIndex = updatedState.findIndex((item) => item.id === id);

      updatedState[updatedIndex] = {
        ...updatedState[updatedIndex],
        isSaved: false
      };

      return updatedState;
    });
  };

解决方案实际上是在 setState 变体中,它使用 callback 之类的 setState((latestState) => {// do something }),并保证每次执行都提供最新的 state

感谢您提供一个美丽的问题来解决。

答案 1 :(得分:0)

所以,这是解决这个问题的另一种方法。

但首先我会找出现有代码中的问题,并尝试解释为什么它不起作用。

我在 console.log 中放置了两个 App.js,一个在 useEffect() 中,另一个在 return 语句之前:

export default function App() {
  const [state, setState] = React.useState([]);

  // Set pannels state
  React.useEffect(() => {
    console.log("useEffect executing");

    setState(
      data.map((item) => ({
        id: item.id,
        isSaved: true,
        content: <Button id={item.id} handleButton={handleButton} />
      }))
    );
  }, []);

  const handleButton = (id) => {
   ....
   ....
  }

  console.log("App Rendering");
  return (
    <div className="App">
      <button onClick={handleClick}>Try this</button>
      {state ? <Tab pannels={state} /> : null}
    </div>
  );
}

以下是执行后的控制台:

enter image description here

现在可以清楚地看到,当您在 setState 内执行 useEffect 时,state 值是一个空数组 []。因为 App 的第一个渲染周期已经完成并且调用 useStatestate 初始化为空数组。

useEffect 中,您将属性 content 分配为:

...
content: <Button id={item.id} handleButton={handleButton} />
...

现在,如果您仔细观察 {handleButton} 是一个绑定到“App”(当然它本身就是一个函数)的第一次渲染的函数,并且 {handleButton} 可以访问 const state它是父作用域,在'App'的第一个渲染周期中是一个空数组[];

因此,当 {handleButton} 正在执行时,您正在将 state 推送到 useEffect 中,该 setState 仍然具有值 [](空数组)。

一旦 useEffectApp 内完成,App 的另一个渲染周期就会执行(由于本地状态更新,react 会重新渲染组件)。将创建 state 的新实例及其所有嵌套成员,但由 setState 管理的 reactApp 除外。

state 的第二次渲染之后,您的 {handleButton} 现在包含 content 的旧实例的引用(您将属性 Button 分配给 App{handleButton} 的先前渲染因此这些 state 仍然具有 Button 值作为 [](空数组闭包)。

现在,当您单击任何 onClick 时,它的 handleButton 绑定到 state 的旧引用,其中 {handleButton} 值为空数组,因此当 {{ 1}} 完成执行它调用 setState (由反应框架提供的函数,不受重新渲染的影响)它再次将 state 设置为空数组。你最终让你的按钮消失了。

现在要解决这个问题,您需要在每次为 {handleButton} 重新渲染时重新绑定 App 的新实例。您无法记住 {handleButton},因为它取决于 state。因此,您可以将 Button 组件的渲染委托给 Tab,后者在每次 state 更新后重新渲染。

所以你可以这样做:

App.js

export default function App() {
  const [state, setState] = React.useState([]);

  // Set pannels state
  React.useEffect(() => {
    setState(
      data.map((item) => ({
        id: item.id,
        isSaved: true
      }))
    );
  }, []);

  const handleButton = (id) => {
    console.log("On button click: ", id);
    console.log(state);

    //Update isSaved of a corresponding button to false
    const updatedState = [...state];
    const updatedIndex = updatedState.findIndex((item) => item.id === id);

    updatedState[updatedIndex] = {
      ...updatedState[updatedIndex],
      isSaved: false
    };

    setState(updatedState);
  };

  return (
    <div className="App">
      <button onClick={() => handleButton(2)}>Try this</button>
      {state ? <Tab pannels={state} handleButton={handleButton} /> : null}
    </div>
  );
}

注意:我什至还为 handleButton 按钮使用了相同的事件处理程序 try this,以表明如果在组件本身上使用或传递给子组件,相同的函数实例也能正常工作。

Tab.js

const Tab = ({ pannels, handleButton }) => {
  return (
    <div>
      List of pannels
      {pannels.map((item) => (
        <div key={item.id}>
          <Button id={item.id} handleButton={handleButton} />
          <span>{item.isSaved ? "true" : "false"}</span>
        </div>
      ))}
    </div>
  );
};

代码沙盒参考:https://codesandbox.io/s/zen-pascal-o6ify

谢谢