reactjs:由于状态未定义/错误,无法映射状态

时间:2018-08-15 18:25:38

标签: javascript reactjs

我无法映射自己的州,无法传递给我的孙子EmailDetails。在尝试在EmailDetails的呈现功能中映射urls数组之前,我可以进行控制台记录。

 import React, { Component } from "react";
    import IssueBox from "./issuebox.js";
    import "./App.css";

    class App extends Component {
      constructor(props) {
        super(props);

        this.state = {
          isLoaded: false,
          emails: [],
          counter: 0,
          title: "Test run",
          value: "",
          selectedEmailId: 0,
          currentSection: "inbox"
        };
      }

      componentDidMount() {
        fetch("blah")
          .then(res => res.json())
          .then(result => {
            const emails = result.data;
            console.log("resutl state: ", emails);
            let id = 0;
            for (const email of emails) {
              email.id = id++;
            }
            this.setState({
              isLoaded: true,
              emails: emails
            });
          });
      }

      handleClickReproducibleCounter(e) {
        let count = this.state.increment
          ? this.state.count + 1
          : this.state.count - 1;

        let increment = this.state.increment;

        if (count === 0) {
          increment = true;
        } else if (count >= 2) {
          increment = false;
        }

        this.setState({
          count,
          increment
        });
      }

      render() {
        return (
          <div className="App">
            <div>
              emails={this.state.emails}
              selectedEmailId={this.state.selectedEmailId}
              onEmailSelected={id => {
                this.openEmail(id);
              }}
              handleClickReproducibleCounter={this.handleClickReproducibleCounter}
            </div>
          </div>
        );
      }
    }

//数据如下:

[
  {
    groupID: "65da6a",
    urls: [
      {
        id: 85,
        searchedurl: "https://www.yahoo.com",
        errorurl: "https://www.yahoo.com/error505",
        count: 1,
        reproducible: false,
        reproducible_counter: 0
      },
      {
        id: 84,
        searchedurl: "https://www.gmail.com",
        errorurl: "https://www.gmail.com/error404",
        count: 1,
        reproducible: false,
        reproducible_counter: 0
      }
    ]
  },
  {
    groupID: "d4127e",
    urls: [
      {
        id: 3,
        searchedurl: "agwscc",
        errorurl: "xyqa",
        count: 1,
        reproducible: false,
        reproducible_counter: 0,
        resolved: null
      }
    ]
  }
];

// issuebox.js

import React, { Component } from "react";
import "./App.css";
import EmailDetails from "./emailDetails";

class IssueBox extends Component {
  constructor(args) {
    super(args);
  }

  render() {

    const currentEmail = this.props.emails.find(
      x => x.id === this.props.selectedEmailId
    );
    console.log("emailDetail view: ", currentEmail); // email obj is present

    return (
      <div className="wrapper">
        <div className="inbox-container">
          <EmailList
            emails={this.props.emails}
            onEmailSelected={this.props.onEmailSelected}
            selectedEmailId={this.props.selectedEmailId}
          />

          <EmailDetails
            email={currentEmail} 
            onReproducibleCounter={this.props.handleClickReproducibleCounter}
            onValidateSumbit={this.props.handleClickValidateSumbission}
            handleTextInputChange={this.props.handleTextInputChange}
          />
        </div>
      </div>
    );
  }
}

export default IssueBox;

// EmailDetails

import React, { Component } from "react";
import "./App.css";

class EmailDetails extends Component {
  constructor(args) {
    super(args);

    this.state = {
      counter: "",
      email: []
    };
  }

  componentDidUpdate(prevProps) {
    if (this.props.email !== prevProps.email) {
      this.setState({ email: this.props.email });
    }
  }

  render() {
    const currentEmail = this.state.email;
    console.log(currentEmail, this.state.email.urls); // able to console obj
    const test = this.state.email.urls.map(obj => {
      //error occurs here
      console.log(obj);
    });
    return (
      <div className="email-content">
        <div className="email-content__header">
          <h3 className="email-content__subject">Issue</h3>
          <div className="email-content__from">Group:</div>
        </div>
        <div className="email-content__message">
          <table role="table">
            <thead role="rowgroup">
              <tr role="row">
                <th role="columnheader">Searched URL</th>
                <th role="columnheader">Error URL</th>
                <th role="columnheader">NR</th>
                <th role="columnheader">Task</th>
              </tr>
            </thead>
            <tbody role="rowgroup" className="group-row" />
          </table>
        </div>

      </div>
    );
  }
}

export default EmailDetails;

以此为起点:codepen

2 个答案:

答案 0 :(得分:1)

我本来要写评论,但要更长一些,所以我决定提供一个答案。

遵循您的代码有点困难,但是我看到了一些问题,还有一些我不理解的事情。

首先,您没有将handleClickReproducibleCounter绑定到App中。因此,您不能将this用于回调函数。将其绑定到构造函数中,或者使用箭头函数代替。

handleClickReproducibleCounter = (e) => {
...
}

第二,您正在EmailDetails组件中使用此功能,例如:

<button onClick={this.props.onReproducibleCounter(obj.id)}>

您为什么要使用this.props?不需要this,而且您已经从道具中破坏了onReproducibleCounter。另外,您这里没有在onClick处理函数中使用回调,而是立即使用参数调用该函数,而这并不是您真正想要的。

所以,应该像这样:

<button onClick={() => onReproducibleCounter(obj.id)}>

但是,使用箭头功能或绑定JSX props中的功能并不是很好,因为它们在每个渲染器中都会重新创建。但是,根据您的情况,更改此逻辑有些棘手。您正在将道具传递给子组件并在其中映射一些东西。一种简单的方法是,在父级中进行映射,然后将单个项目传递给子级组件。

现在,我不了解的事情。您正在将一些obj.id传递给回调函数,但正在等待e吗?看来您在函数中的任何地方都没有使用此参数。

我不了解的第二件事是在您的地图中,您使用的第二个参数为onReproducibleCounter

email.urls.map((obj, onReproducibleCounter) => {

此处的第二个参数是index

评论后更新

您正在使用异步作业在父级中获取此数据。当您尝试在this.state.email.urls上进行映射时,那时没有this.state.email.urls,那么您会收到错误消息。

只需使用条件渲染或日志记录即可。

(this.state.email.urls || [] ).map(obj => {
      //error occurs here
    console.log(obj);
});

因此,如果没有email.urls,那么我们的地图将使用一个空数组,这对我们来说是可以的。提取完成后,该组件将重新呈现,它将映射实际数据。

但是,不要将道具变成孩子的状态。在此处使用email道具。如果您需要对其进行处理,请在您的子组件中进行处理。

这是一个非常简单而愚蠢的示例:

const Child = (props) => {
  const doSomething = () => {
    const newNumber = props.number * 20;
    props.handleNumber( newNumber );
  }
  return (
    <div>
    <button onClick={doSomething}>Click me, so do something in the Child and change this number.</button>
    </div>
  );
}

class App extends React.Component {
  state = {
    number: 10,
  }

  handleNumber = number => this.setState({number});

  render() {
    return <div>
    <p>Number is: {this.state.number}</p>
    <Child number={this.state.number} handleNumber={this.handleNumber} />
    </div>;
  }
}

ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>

答案 1 :(得分:0)

希望我能理解您的要求,当您将此类回调传递给孩子时,您需要确保this关键字指向正确的内容。

最简单的方法是使用fat arrow函数,

 handleClickReproducibleCounter(e) {

成为

 handleClickReproducibleCounter = e => {

您还可以在constructor函数中添加一行,以完成此操作

this.handleClickReproducibleCounter = this.handleClickReproducibleCounter.bind(this)

这样做是为了确保在调用handleClickReproducibleCounter时,this.state始终指代父组件的状态(或其绑定到的任何组件)。

关于您的问题:

  

这是否意味着我需要跟踪孙子中的单独状态?

正确,您不需要。只要回调函数已正确绑定到父组件状态,便可以将回调传递至任意范围,并且您可以在应用程序中的任意位置更改该状态。