无法在嵌套对象 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" />
我在根据输入更新嵌套对象的状态时遇到问题。
更新了更多代码。我正在尝试构建一个多步骤表单。第一页取球队信息,第二页取球员信息。
父组件:
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" />