【问题标题】:React State Update rendering on second click第二次点击时反应状态更新渲染
【发布时间】:2021-03-15 23:50:17
【问题描述】:

我一直在挠头试图弄清楚这一点,尝试了我能想到/在网上找到的所有东西,但没有运气。 当组件挂载时,我从 API 中检索一个列表,对其进行解析并将其推送到我所在州的另一个数组中。我检查了 api,它工作正常,我得到数据,在调试时我看到逻辑运行正常,我的控制台日志按预期触发。我看到的唯一问题是渲染。

我正在 ComponentDidMount 中进行以下调用:

ScheduleService.getScheduleList().then((response) =>
  response.data.scheduleList.forEach((schedule: any, index: any) =>
    this.state.nodes?.push({
      id: index,
      label: (
        <div onClick={(e) => this.handleScheduleClick(e)}>
          <Tooltip2
            content={
              "Start: " +
              schedule.startTime.utc +
              " Stop: " +
              schedule.stopTime.utc +
              " Type: " +
              schedule.scheduleType
            }
            placement="right"
          >
            {schedule.id}
          </Tooltip2>
        </div>
      ),
      isExpanded: false,
      icon: "document",
    })
  )
);

我尝试在回调中添加此调用.. 不走运。尝试将值分配给数组并随后更新状态,结果相同。

当我第一次单击路由器并进入页面时,我的树没有填充。 Data not rendered

但在我立即切换回此页面后,我的数据呈现出来。

Data rendered

我知道每次状态更改时 React 都会更新组件,但我不明白为什么现在没有发生。

也试过运行this.forceUpdate(),结果一样。

非常感谢您的帮助!

编辑 1: 我在下面写了我正在使用的逻辑。

 public state: IState= {
        nodes: ITreeNode[],
    }
    
    class MyClass extends React.Component<IState> {
    
    public componentDidMount(){
       this.getScheduleList();
    }

   private getScheduleList = () => {
     ScheduleService.getScheduleList().then((response) =>
      response.data.scheduleList.forEach((schedule: any, index: any) =>
        this.state.nodes?.push({
          id: index,
          label: (
            <div onClick={(e) => this.handleScheduleClick(e)}>
              <Tooltip2
                content={
                  "Start: " +
                  schedule.startTime.utc +
                  " Stop: " +
                  schedule.stopTime.utc +
                  " Type: " +
                  schedule.scheduleType
                }
                placement="right"
              >
                {schedule.id}
              </Tooltip2>
            </div>
          ),
          isExpanded: false,
          icon: "document",
        })
      )
    );
    public render() {
        return (
<div>
         <Tree
                      contents={this.state.nodes}
                        className={
                        " focus-panelsList focus-panelsList-noborder"
                      }
                    />
                  </div>
            }
    )

【问题讨论】:

  • this.state.nodes.push({}) 是一个状态突变,this.setState(this.state) 似乎是一个无用的更新。您只是想填充this.state.nodes 状态吗?能否提供更全面的代码示例?
  • 我已经编辑了帖子并为我正在尝试的更新方法添加了整个逻辑。我有一个节点列表的接口,我正在将数据推送到我在状态中设置的列表中。我将该节点列表传递给 BlueprintJS 树。
  • 你真的不应该将 React 组件存储在状态中,这是拥有陈旧状态/回调外壳的可靠方法。它应该是 just 数据,然后在渲染时将数据映射到 JSX。你真正想做什么?你能提供一个Minimal, Complete, and Reproducible Code Example吗?
  • 现在编辑帖子。
  • @DrewReese 我已经用我正在使用的逻辑更新了代码。不要认为我拥有的其他代码会影响这一点。至于 JSX 部分,我正在使用一个库并按照他们的示例进行操作。到目前为止,我不知道如何将 JSX 注入该树中,该组件不是由我构建的。

标签: reactjs typescript


【解决方案1】:

首先,您不能直接编辑状态。为了更新状态并做出反应以识别状态变化是通过使用this.setState 方法。 其次,您不应该将组件存储在状态中。您可以将所需的数据存储在 state 和组件的 render 方法中,使用 map 函数通过nodes 并生成标签组件。 我重构了getScheduleList 方法。 '我已对其进行了更新,因此它将节点设置为该数组,而不是在每次迭代时更新状态。

private getScheduleList = () => {
  ScheduleService.getScheduleList().then((response) => {
    const nodesData = response.data.scheduleList.map((schedule: any, index: any) => ({
      id: index,
      schedule: schedule,
      label: {
        content: "Start: " +
          schedule.startTime.utc +
          " Stop: " +
          schedule.stopTime.utc +
          " Type: " +
          schedule.scheduleType,
          id: schedule.id,
      },
      isExpanded: false,
      icon: "document",
    }));
    this.setState({nodes: nodesData})
  });
}

然后你可以在你的渲染方法中生成标签组件

public renderLabels() {
  return (
    <div>
      {this.state.nodes.map(n => {
        return (
          <div key={"node-label-" + n.id} onClick={(e) => this.handleScheduleClick(e)}>
            <Tooltip2
              content={n.label.content}
              placement="right"
            >
              {n.label.id}
            </Tooltip2>
          </div>
        );
      })}
    </div>
  );
}

public render() {
  return this.renderLabels());
}

或者,由于您需要在 Tree 组件的节点数组中自定义标签组件,您可以这样做。

public renderNodeLabel(node) {
  return (
    <div key={"node-label-"+node.id} onClick={(e) => this.handleScheduleClick(e)}>
      <Tooltip2
        content={node.label.content}
        placement="right"
      >
        {node.label.id}
      </Tooltip2>
    </div>
  );
}

public render() {
  return (
    <Tree
      contents={this.state.nodes.map(n => ({
        ...n,
        label: this.renderNodeLabel(n),
      }))}
      className="focus-panelsList focus-panelsList-noborder"
    />
  )
}

【讨论】:

  • 非常感谢!现在加载正常。我也会尝试整合渲染标签。我正在从 BlueprintJS 扩展一个接口来填充 Tree 组件。老实说,我不确定如何传递该工具提示如果我不在数组中创建 JSX,则不确定如何集成您在该 Tree 组件中提供的 RenderLabels 方法。
  • 我已经更新了答案。检查第三个代码块
  • 非常感谢@HaseebA 的帮助。我已经得到它与德鲁的建议一起工作。明天我也会试试你的,我已经死了,在我的国家是凌晨 3 点。
  • 这也很有魅力。不得不改变一点节点的状态,但工作正常。再次感谢!
【解决方案2】:

问题

看来您的问题是状态突变。如果您正在加载数据并填充 this.state.nodes 状态数组,那么您不应该进入当前状态。

使用功能状态更新从任何先前存在的状态进行更新,并附加新映射的空对象数组。

其次,您将 JSX 存储在一种 React 反模式的状态中。您应该仅存储数据,然后将状态映射到您需要传递给第三方模块的 JSX。

ScheduleService.getScheduleList()
  .then((response) => {
    this.setState((prevState: IState) => ({
      nodes: prevState.nodes.concat(response.data),
    }));
  });

将状态映射到 JSX

const data = this.state.nodes.map((schedule: any, index: any) => ({
  id: index,
  label: (
    <div onClick={this.handleScheduleClick}>
      <Tooltip2
        content={
          "Start: " +
          schedule.startTime.utc +
          " Stop: " +
          schedule.stopTime.utc +
          " Type: " +
          schedule.scheduleType
        }
        placement="right"
      >
        {schedule.id}
      </Tooltip2>
    </div>
  ),
  isExpanded: false,
  icon: "document",
}));

将data 传递给第三方组件,而不是您之前可能使用的this.state.nodes。

除此之外,您还没有初始状态。您应该提供有效的初始状态。

class MyClass extends React.Component<IState> {
  state = {
    nodes: [],
  };

  public componentDidMount(){
    this.getScheduleList();
  }

【讨论】:

  • 我已经尝试实现,但在 prevState.nodes 上出现以下错误:属性 'nodes' 在类型 'Readonly'.ts(2339) 上不存在
  • @EmilianMarin 您需要输入prevState 变量,它与您的this.state 相同。
  • 非常感谢您的帮助,我还按照您的建议分离了 JSX。至于我之前提到的状态错误,我现在明白了为什么会出现该错误。我今天刚开始在 TSX 上使用 React,一直忘记我需要在所有东西上声明类型。
  • 最后一个问题,您建议在哪里保留数据和状态之间的映射?我已经在渲染块中声明了 const 。把它放在这里是个好习惯吗?或者我应该从方法中返回它吗?
  • @EmilianMarin 在渲染函数中声明和计算很好。如果可以,或者您更喜欢考虑实用功能,这真的归结为偏好。两者都可以工作并执行相同的操作。
猜你喜欢
  • 1970-01-01
  • 2018-08-11
  • 2021-10-28
  • 1970-01-01
  • 2021-01-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多