React Immutable Generic Arrow更新受控字段的嵌套状态

时间:2018-05-27 12:27:24

标签: reactjs

我正在寻找更新一个或多个字段的最佳方法,使用基于event.target.name的泛型函数,而不会改变状态。

示例:

  onInputChange = event => {
    const newEvent = this.state.event
    newEvent[event.target.name] = event.target.value
    this.setState({
      event: newEvent
    })
  }

上述功能可以正常工作。然而,它正在改变setState方法之前的状态,因为newEvent是一个引用。

使用spread运算符复制状态不能按预期工作。 我缺少什么?

请参阅下面的完整代码和更深入的分析。

import React, { Component } from 'react'

class EventForm extends Component {
  state = {
    event: {
      title: '',
      venue: ''
    }
  }

  onFormSubmit = event => {
    event.preventDefault()
    //..
  }

  onInputChange = event => {
    const newEvent = this.state.event
    newEvent[event.target.name] = event.target.value
    this.setState({
      event: newEvent
    })
  }

  render() {
    const { event } = this.state

    return (
      <div>
        <div>
          Title: {this.state.event.title}
          <br />
          Venue: {this.state.event.venue}
        </div>
        <form onSubmit={this.onFormSubmit}>
          <label>Event Title</label>
          <input
            placeholder="title"
            name="title"
            onChange={this.onInputChange}
            value={event.title}
          />
          <label>Venue</label>
          <input
            name="venue"
            placeholder="Event Venue"
            onChange={this.onInputChange}
            value={event.venue}
          />
          <button type="submit">
            Submit
          </button>
          <button type="button" onClick={this.props.onCancel}>
            Cancel
          </button>
        </form>
      </div>
    )
  }
}

export default EventFort

newEvent绝对是对状态的引用,因此它直接改变状态。下面的代码证明:

onInputChange = event => {
  const newEvent = this.state.event
  newEvent[evt.target.name] = event.target.value
  console.log(this.state.event) ​// => this.state.event {title: "a", venue: ""}
}

好了,现在证明状态是由控制台日志直接改变的,我的第一直觉是使用扩展运算符制作一个状态副本。

onInputChange = event => {
  const newEvent = [...this.state.event] 
  newEvent[event.target.name] = event.target.value
  this.setState({
    event: newEvent
  })
}

然而它可以正常工作,只要我输入输入,我就会在控制台上收到以下错误:

Warning: A component is changing a controlled input of type
undefined to be uncontrolled.
Input elements should not switch from controlled to uncontrolled (or 
vice versa).
Decide between using a controlled or uncontrolled input element
for the lifetime of the component. More info:
in input (at EventForm.jsx:43)
in form (at EventForm.jsx:34)
in div (at EventForm.jsx:28)
in EventForm (at EventDashboard.jsx:90)
in div (created by GridColumn)
in GridColumn (at EventDashboard.jsx:87)
in div (created by Grid)
in Grid (at EventDashboard.jsx:83)
in EventDashboard (at App.jsx:14)
in div (created by Container)
in Container (at App.jsx:13)
in div (at App.jsx:11)
in App
in AppContainer (at index.js:16)

所以我确信有比原始代码更好的解决方案,即使它正在工作,它似乎也不是最好的方法。

1 个答案:

答案 0 :(得分:1)

当您创建新对象时,不要直接在状态的对象上工作,只需将状态展开并覆盖值。
您可以使用computed keys将其设为通用:

  onInputChange = ({target}) => {
    const { event } = this.state;
    const newEvent = {
      ...event,
      [target.name]: target.value
    }
    this.setState({
      event: newEvent
    })
  }

运行示例:

class EventForm extends React.Component {
  state = {
    event: {
      title: "",
      venue: ""
    }
  };

  onFormSubmit = event => {
    event.preventDefault();
    //..
  };

  onInputChange = ({ target }) => {
    const { event } = this.state;
    const newEvent = {
      ...event,
      [target.name]: target.value
    };
    this.setState({
      event: newEvent
    });
  };

  render() {
    const { event } = this.state;

    return (
      <div>
        <div>
          Title: {this.state.event.title}
          <br />
          Venue: {this.state.event.venue}
        </div>
        <form onSubmit={this.onFormSubmit}>
          <label>Event Title</label>
          <input
            placeholder="title"
            name="title"
            onChange={this.onInputChange}
            value={event.title}
          />
          <label>Venue</label>
          <input
            name="venue"
            placeholder="Event Venue"
            onChange={this.onInputChange}
            value={event.venue}
          />
          <button type="submit">Submit</button>
          <button type="button" onClick={this.props.onCancel}>
            Cancel
          </button>
        </form>
      </div>
    );
  }
}

ReactDOM.render(<EventForm />, 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>