尝试更新待办事项应用程序中的列表时无法调用 setState()

Cannot call setState() when attempting to update list in todo app

这似乎是一个常见问题,但我已经尝试了多种配置,并且始终收到错误消息 Warning: Can't call setState on a component that is not yet mounted。我在我的应用程序的其他组件中以类似的方式使用 setState() 没有问题,所以我不确定为什么我的 App 组件认为我正在尝试在构造函数中调用 setState()很明显它在外面。

这是我尝试使用componentDidMount()生命周期方法。显然我完全遗漏了一些东西,但我无法阅读 setState() 的文档。 特别是 考虑到我在我的应用程序的其他部分使用了这种完全相同的语法,没有任何问题。无论如何,这就是我正在使用的东西:

import react from 'react';

class App extends react.Component {
  constructor() {
    super();
    this.state = {
      tasks: [
        {
          content:"walk dog",
          date:"7/17/21",
          priority:"high"
        },
        {
          content:"take out trash",
          date:"7/17/21",
          priority:"low"
        },
      ],
    };
  }
  componentDidMount() {
    this.addTask = this.addTask.bind(this);
  }
  addTask(content, date, priority) {
    let taskUpdate = this.state.tasks;
    let task = {
      content: content,
      date: date,
      priority: priority
    };
    taskUpdate.push(task);
    this.setState({
      tasks: taskUpdate
    });
  }

  render() {
    return (
      <>
        <Header />
        <Sidebar taskList={this.state.tasks} groupList={this.state.groups}>
        </Sidebar>
      </>
    )
  }
}

export { App };

要点是更新我的 App 组件的状态,以便我可以作为 props 传递并重新呈现 Sidebar 组件中的任务列表。提前道歉,因为这似乎已被问过一百万次,但我只是感到困惑。

编辑

为澄清起见,addTask 方法是在作为“将此任务添加到任务列表”模式的一部分呈现的表单中调用的,例如:

const AddModal = ({ closeBtn, show, children }) => {
    class Form extends Component {
        constructor(props) {
            super(props);
            this.state = {
                content: '',
                date: '',
                highPriority: ''
            };
            this.handleContent = this.handleContent.bind(this);
            this.handleDate = this.handleDate.bind(this);
            this.handlePriority = this.handlePriority.bind(this);
            this.handleSubmit = this.handleSubmit.bind(this);
        }
        handleContent(event) {
            this.setState({content: event.target.value});
        }
        handleDate(event) {
            this.setState({date: event.target.value});
        }
        handlePriority(event) {
            this.setState({highPriority: event.target.checked});
        }
        handleSubmit(event) {
            event.preventDefault();
            console.log("content: " + this.state.content + "\n" + 
                        "date: " + this.state.date + "\n" +
                        "high priority: " + this.state.highPriority);
            new App().addTask(this.state.content, this.state.date, this.state.highPriority);
            closeBtn();
            
        }
        render() {
            return (
                <form onSubmit={this.handleSubmit}>
                    <input 
                    type="text"
                    value={this.state.content} 
                    onChange={this.handleContent}
                    placeholder="add a task (max 30 chars)"
                    maxLength="30"></input>
    
                    <input 
                    type="date"
                    onChange={this.handleDate}></input>
    
                    <div className="checkbox-container">
                        <input
                        type="checkbox"
                        name="priority-checkbox"
                        onChange={this.handlePriority}></input>
                        <label
                        htmlFor="priority-checkbox">
                            high priority?
                        </label>
                        <button
                            type="submit"
                            id="add-task-submit">
                            +
                        </button>
                    </div>
    
                    
                </form>
            );
        }
    };

    const modalClassName = show ? "add-task-modal display-block" : "add-task-modal display-none";
    return ( 
        <div className={modalClassName}>
            <div className="add-task-modal-content">
                {children}
                <button type="button" id="close-btn" onClick={closeBtn}>
                    x
                </button>

                <Form></Form>
            </div>
        </div>
    );
};

包含此表单的模态呈现在一个仅显示任务列表的文件中。 addTask() 方法成功地将表单数据传递给 'App.js',因为我可以直接更改状态,例如this.state.tasks = [...],但据我所知,这绝对不是最佳实践,我需要改用 setState()

编辑 2

因此,当我在改变状态的一个简单问题上跌跌撞撞时,开始变成了对 React 作为框架的简洁而有用的概述。非常感谢用户 Robin Zigmond 的撰写,并感谢 Alexander Staroselsky 进一步阐述了使用 redux 库的建议。您的意见帮助很大,我的应用程序的组件现在可以正确地相互通信。

只要说我最初的错误并不是 setState 的真正问题就够了,这有助于我在更广泛的层面上理解 React。再次感谢所有贡献的人。

你不应该就地改变数组,当你使用 push 对数组的引用不会改变。但不确定这是否与您的警告有关。

  addTask(content, date, priority) {
    this.setState({
      tasks: this.state.tasks.concat({
        content,
        date,
        priority
      })
    });
  }

为什么用 new 调用 addTask?你不应该这样做。

new App().addTask(this.state.content, this.state.date, this.state.highPriority);

可以使用contextredux传递值和更新函数到模态。

您可以将 addTask 函数替换为:

addTask(content, date, priority) {
    
    let task = {
      content: content,
      date: date,
      priority: priority
    };
    
    this.setState({
      tasks: [...this.state.tasks, task]
    });
  }

问题可以在您的 AddModal 组件中看到:

new App().addTask(this.state.content, this.state.date, this.state.highPriority);

手动为您的组件之一创建一个新实例 - 正如您在此处使用 new App() 所做的那样 - 不是您在 React 中永远需要做的事情。框架本身处理所有组件的生命周期——包括构建它们。

在这种情况下,警告本身就暴露了:“无法在尚未安装的组件上调用 setState”。 “mounted”是 React 中的一个概念,它仅指组件实例是否已呈现为用户界面的一部分——换句话说,它是否在页面上。 React 是一个 library/framework,其唯一目的是构建用户界面 - 每个组件都应该是您可以将 non-technical 用户指向 page/app 的一部分并说“那是此组件控制的部分”。在页面上拥有一个实际未呈现(或“挂载”以使用 React 术语)的组件实例没有任何意义。

从技术上讲,在准备好“挂载”组件之前,React 甚至不会调用您的 class 的构造函数 - 因此在调用构造函数之后,React 将调用您的组件的 render 方法然后(如果它有一个)它的 componentDidMount 方法。 (请参阅 React 组件生命周期图 here。)当您在 render 内的 JSX 中包含组件时,React 会自行处理所有这些 - 因此您不必担心。但是通过手动实例化 new App() 你是在构建一个组件实例而不安装它,React 不是设计来处理的,因此它有理由抱怨。

您似乎试图做的事情在更深层次上没有意义 - 因为您正在尝试更新组件状态,但组件不应该 状态尚未安装。同样,状态应该对应于 UI 中的某些内容,即使是在稍微抽象的层面上——它可能是用户当前在输入字段中输入的内容,或者更抽象的东西,比如标志某些动作已经完成或未完成,或者某个特定动作已经完成的次数 - 或者您想要的任何其他内容。但它对应于 something,并且该 something 与页面上组件内部发生的事情密切相关。如果它不在页面上 - 那么您的状态实际上是什么?

到 return 你实际应该做什么:我假设你的 App 是你应用程序的根组件,并且 AddModal 出现在它里面的某个地方。如果它是一个直接的 child,这将很简单,因为你的 App 总是在 AddModal 出现时呈现,你可以简单地将 addTask 方法作为 prop 传递下去。换句话说,在 Apprender 内的某处,您将拥有:

<AddModal addTask={ this.addTask } />

然后在 AddModal 中称其为:

this.props.addTask(/*whatever arguments you need*/);

这会将 parent 组件的状态更新为您需要的状态。 (这里似乎正在更新 Sidebar 组件内的数据。)

如果看起来 AddModal 实际上下降了几级,则您必须将 addTask 下降几级。 IE。在 Apprender 内,您将拥有

<SomeChildComponent addTask={ this.addTask } />

然后在SomeChildComponent里面,说

<SomeOtherChild addTask={ this.props.addTask } />

依此类推,直到您最终达到

<AddModal addTask={ this.props.addTask } />

如果你有超过 1 或 2 个级别并且不需要中间级别的 addTask,这肯定会变得乏味和脆弱 - 所以你可能想看看其他解决方案,如上下文, 或 Redux。但这就是你在“基本 React”中做事的方式,我认为你最好先理解这一点,然后再仔细研究其他模式。 (我坚信在开始使用 Redux 之类的库之前需要了解它们正在解决什么问题,因为“其他人都这样做”。)

我还建议阅读 this 以了解如何处理一个组件需要更新另一个不是直接 child 或 [=77 的状态的情况=] - 这似乎是你想要做的。

抱歉,我一直在胡说八道,但我希望这能让您更好地了解 React 的工作原理以及如何处理状态,以及为什么您不应该手动构建自己的组件。