【问题标题】:Unable to set state on nested object reactjs无法在嵌套对象 reactjs 上设置状态
【发布时间】:2020-08-26 12:57:08
【问题描述】:

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

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

父组件:

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>
            )
    }

}

}

接下来的代码 sn-p 是表单的第一页和一个子组件。 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 名玩家添加到团队中,因此父组件中的玩家数组。请让我知道我是否应该以另一种方式解决这个问题。提前谢谢!

【问题讨论】:

  • 您将字符串而不是变量传递给handleChange 函数。
  • 我把它改成了:onChange={handleChange(values.player.firstName)},但还是不能设置
  • 另外,除了我的第一条评论之外,您更新状态的方法似乎是错误的。在这个当前形状中,它使用输入中的给定值创建一个玩家名字。它不会更新您所在州的播放器数组。所以,请与我们分享更多代码。你如何传递道具以及你输入的意图是什么?
  • 不工作是什么意思?函数是否没有被调用/它传递了错误的值,从而错误地更新了状态/值是正确的但状态没有更新?您可以在handleChange 中添加console.log 进行检查。 :)
  • 用更多代码更新了主帖 :) 谢谢!

标签: reactjs input state onchange nested-object


【解决方案1】:

对于FormPlayerDetails,您不需要传递完整的值,而只需传递values.players。而且由于您的玩家属性是一个表格......请改用地图功能

【讨论】:

    【解决方案2】:

    您所在州的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,因为我们不想显示它。

    您可以在下面找到工作的 sn-p。

    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" />

    【讨论】:

    • 谢谢!但是,在执行 handleChange 之后,仍然没有从输入值更新 firstName。另外,你能向我解释一下 ...prev.player 是如何工作的吗?我对 js 还是很陌生。谢谢!
    • 你可以看到我的sn-p。它适用于我的情况。只需根据此更改相关部分即可。不要忘记更改您所在州的player。它应该是一个对象,而不是一个数组。 prev 部分与setState 的功能更新有关,... 是扩展语法。谷歌他们:)如果你还有问题,我会尽力解释。
    • 谢谢!你的解决方案奏效了!最初我将player 定义为一个数组而不是一个对象,因为在我的PlayerForm 中,我希望用户能够为多个玩家输入信息。以player 为对象,我如何能够为多个玩家获取输入?
    • 如果您将玩家信息作为对象保存,您将无法做到这一点。我已经更新了我的答案并添加了一个使用玩家数组的版本。
    • 您的解决方案运行顺畅。非常感谢您的帮助!
    猜你喜欢
    • 2020-11-02
    • 2022-07-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-03-28
    • 2021-09-03
    • 2016-04-29
    • 1970-01-01
    相关资源
    最近更新 更多