无法在嵌套对象 reactjs 上设置状态

Unable to set state on nested object reactjs

我在根据输入更新嵌套对象的状态时遇到问题。

更新了更多代码。我正在尝试构建一个多步骤表单。第一页取球队信息,第二页取球员信息。

父组件:

export class MainForm extends Component {
state = {
    step: 1,
    teamName: '',
    teamManagerName: '',
    teamManagerEmail: '',
    player: [{
        firstName: '',
        lastName: '',
        email: '',
        height: ''
    }]
}

// proceed to next step
nextStep = () => {
    const { step } = this.state;
    this.setState({
        step: step + 1
    })
}

// go to previous step
prevStep = () => {
    const { step } = this.state;
    this.setState({
        step: step - 1
    })
}

// handle fields change
handleChange = input => e => {
    this.setState({[input]: e.target.value});
}

render() {
    const { step } = this.state;
    const { teamName, teamManagerName, teamManagerEmail, player: { firstName, lastName, email, height}} = this.state;
    const values = {teamName, teamManagerName, teamManagerEmail, player: { firstName, lastName, email, height}};

    switch(step) {
        case 1:
            return (

                <FormTeamDetails
                    nextStep={this.nextStep}
                    handleChange={this.handleChange}
                    values={values}
                />
                )
        case 2:
            return (
               <FormPlayerDetails
                    nextStep={this.nextStep}
                    prevStep={this.prevStep}
                    handleChange={this.handleChange}
                    values={values}
               />
            )
        case 3:
            return (
                <Confirm
                    nextStep={this.nextStep}
                    prevStep={this.prevStep}
                    values={values}
           />
            )
        case 4:
            return (
                <h1>Scuccess!</h1>
            )
    }

}

}

下一个代码片段是表单的第一页和一个子组件。 handleChange 函数在这里成功完成了它的工作。

export class FormTeamDetails extends Component {
continue = e => {
    e.preventDefault();
    this.props.nextStep();
}

render() {
    const { values, handleChange } = this.props;
    console.log(this.props)
    return (
        <Container>
            <Form>
                <FormGroup>
                    <Label for="teamName">Team Name</Label>
                    <Input 
                        type="teamName" 
                        name="teamName" 
                        onChange={handleChange('teamName')} 
                        defaultValue={values.teamName} />
                    <Label for="teamManagerName">Team Manager Name</Label>
                    <Input 
                        type="teamManagerName" 
                        name="teamManagerName" 
                        onChange={handleChange('teamManagerName')} 
                        defaultValue={values.teamManagerName}
                        />
                    <Label for="teamManagerEmail">Team Manager Email</Label>
                    <Input 
                        type="teamManagerEmail" 
                        name="teamManagerEmail" 
                        onChange={handleChange('teamManagerEmail')} 
                        defaultValue={values.teamManagerEmail} />

                    <Button 
                        label="Next"
                        // primary="true"
                        onClick={this.continue}
                        style={{float: 'right'}}>Next</Button>
                </FormGroup>
            </Form>
        </Container>

    )
}

}

这是表格的第二页,我遇到问题的地方:

export class FormPlayerDetails extends Component {
continue = e => {
    e.preventDefault();
    this.props.nextStep();
}

back = e => {
    e.preventDefault();
    this.props.prevStep();
}

render() {
    const { values: { player }, handleChange, values } = this.props;

    return (
        <Container>
            <h3>Add players</h3>
            <Form>
            <Row form>
                <Col md={3}>
                <FormGroup>
                    <Input type="firstName" 
                    name="firstName" 
                    id="firstName" 
                    placeholder="First Name" 
                    defaultValue={values.player.firstName} 
                    onChange={handleChange('values.player.firstName')} 
                    />
                </FormGroup>
                </Col>
                <Col md={3}>
                <FormGroup>
                    <Input type="lastName" name="lastName" id="lastName" placeholder="Last Name"  />
                </FormGroup>
                </Col>
                <Col md={3}>
                <FormGroup>
                    <Input type="email" name="email" id="email" placeholder="Email" />
                </FormGroup>
                </Col>
            </Row>

            <Row form>
                <Col>
                <Button 
                        label="Back"
                        // primary="true"
                        onClick={this.back}
                        style={{float: 'left'}}>Back</Button>
                </Col>
                <Col>
                <Button 
                        label="Next"
                        // primary="true"
                        onClick={this.continue}
                        style={{float: 'right'}}>Next</Button>
                </Col>
            </Row>

            </Form>
        </Container>

    )
}

}

我无法使用输入值更新播放器 属性。另外,我想在此组件中添加多个播放器输入字段。我希望至少有 5 个玩家被添加到团队中,因此父组件中的玩家数组。请让我知道我是否应该以其他方式解决这个问题。提前致谢!

对于FormPlayerDetails,您不需要传递完整的值,而是只需要传递values.players。并且由于您的 players 属性是 table... 请改用 map 函数

你的 player 属性 在你的状态下是一个数组,但你把它当作一个对象。所以,我假设会有一个玩家,它将是一个对象。这是您的案例的一种解决方案。但感觉就像你正在努力处理许多不必要的事情,比如传递值等,因为你已经为你的输入设置了 name 属性。我不知道,也许你在考虑其他情况。

这是handleChange部分:

handleChange = input => e => {
  const { target } = e;
  if (input === "player") {
    this.setState(prev => ({
      player: {
        ...prev.player,
        [target.name]: target.value
      }
    }));
  } else {
    this.setState({ [input]: target.value });
  }
};

这里是输入部分:

<Input
  type="firstName"
  name="firstName"
  id="firstName"
  placeholder="First Name"
  defaultValue={values.player.firstName}
  onChange={handleChange("player")}
/>

如您所见,我正在传递 handleChange player 属性 并从您的输入中获取 name。然后在 handleChange 方法中,使用功能状态和扩展语法,我正在更新 player.

class App extends React.Component {
  state = {
    step: 1,
    teamName: "",
    player: {
      firstName: "",
      lastName: "",
      email: ""
    }
  };

  handleChange = input => e => {
    const { target } = e;
    if (input === "player") {
      this.setState(prev => ({
        player: {
          ...prev.player,
          [target.name]: target.value
        }
      }));
    } else {
      this.setState({ [input]: target.value });
    }
  };

  render() {
    return (
      <div>
        <Input handleChange={this.handleChange} />
        <div>{JSON.stringify(this.state)}</div>
      </div>
    );
  }
}

class Input extends React.Component {
  render() {
    const { handleChange } = this.props;
    return (
      <div>
        <div>
          <span>firstName</span>
          <input
            type="firstName"
            name="firstName"
            id="firstName"
            onChange={handleChange("player")}
          />
        </div>
        <div>
          <span>team</span>
          <input
            type="teamName"
            name="teamName"
            onChange={handleChange("teamName")}
          />
        </div>
      </div>
    );
  }
}

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

这里是 players 数组的更新版本(注意复数名称)。所以,在这个天真的版本中,我们保留了一个 players 数组,上面有一些独特的 id。这就是我们将如何定位更新后的播放器。检查每个 input,现在有一个 id,我们将其设置为 player 的一个。在 handleChange 函数中,这次我们正在映射 players (因此返回一个新数组),然后如果 id 与我们玩家的匹配,我们正在更新其相关的 属性。如果 id 不匹配,我们将返回原始 player 而不做任何事情。

handleChange 部分是您需要重点关注的部分。我们通过使用 Input 组件中每个 player 的属性来映射和渲染输入。这似乎有点令人困惑,但如果你深入研究它,你就会明白。我们正在使用 Object.entries 来映射属性,但在此之前,我们正在清理 id 一个,因为我们不想显示它。

您可以在下面找到工作片段。

class App extends React.Component {
  state = {
    step: 1,
    teamName: "",
    players: [
      {
        id: 1,
        firstName: "foo",
        lastName: "foo's lastname",
        email: "foo@foo.com"
      },
      {
        id: 2,
        firstName: "bar",
        lastName: "bar's lastname",
        email: "bar@bar.com"
      }
    ]
  };

  handleChange = input => e => {
    const { target } = e;
    if (input === "players") {
      this.setState({
        players: this.state.players.map(player => {
          if (player.id === Number(target.id)) {
            return { ...player, [target.name]: target.value };
          }

          return player;
        })
      });
    } else {
      this.setState({ [input]: target.value });
    }
  };

  render() {
    return (
      <div>
        <Input handleChange={this.handleChange} players={this.state.players} />
        <div>
          {this.state.players.map(player => (
            <div>
              <h3>Player {player.id}</h3>
              <div>First Name: {player.firstName}</div>
              <div>Last Name: {player.lastName}</div>
              <div>Email: {player.email}</div>
            </div>
          ))}
        </div>
      </div>
    );
  }
}

class Input extends React.Component {
  render() {
    const { handleChange, players } = this.props;
    return (
      <div>
        {players.map(player => {
          const { id, ...playerWithoutId } = player;
          return (
            <div key={player.id}>
              <h3>Player {player.id}</h3>
              {Object.entries(playerWithoutId).map(([key, value]) => (
                <div>
                  <span>{key}</span>
                  <input
                    name={key}
                    id={player.id}
                    onChange={handleChange("players")}
                    defaultValue={value}
                  />
                </div>
              ))}
            </div>
          );
        })}
      </div>
    );
  }
}

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