更新道具的状态在React Form中更改

时间:2015-09-05 14:39:45

标签: reactjs

我在使用React表单时遇到问题并正确管理状态。我在表单中有一个时间输入字段(在模态中)。初始值在getInitialState中设置为状态变量,并从父组件传入。这本身就可以。

当我想通过父组件更新默认的start_time值时,会出现问题。更新本身通过setState start_time: new_time在父组件中进行。但是在我的表单中,默认的start_time值永远不会更改,因为它只在getInitialState中定义一次。

我尝试使用componentWillUpdate通过setState start_time: next_props.start_time强制更改状态,这确实有效,但却给我Uncaught RangeError: Maximum call stack size exceeded次错误。

所以我的问题是,在这种情况下更新状态的正确方法是什么?我在某种程度上想到了这个错误吗?

当前代码:

@ModalBody = React.createClass
  getInitialState: ->
    start_time: @props.start_time.format("HH:mm")

  #works but takes long and causes:
  #"Uncaught RangeError: Maximum call stack size exceeded"
  componentWillUpdate: (next_props, next_state) ->
    @setState(start_time: next_props.start_time.format("HH:mm"))

  fieldChanged: (fieldName, event) ->
    stateUpdate = {}
    stateUpdate[fieldName] = event.target.value
    @setState(stateUpdate)

  render: ->
    React.DOM.div
      className: "modal-body"
      React.DOM.form null,
        React.createElement FormLabelInputField,
          type: "time"
          id: "start_time"
          label_name: "Start Time"
          value: @state.start_time
          onChange: @fieldChanged.bind(null, "start_time”)

@FormLabelInputField = React.createClass
  render: ->
    React.DOM.div
      className: "form-group"
      React.DOM.label
        htmlFor: @props.id
        @props.label_name + ": "
      React.DOM.input
        className: "form-control"
        type: @props.type
        id: @props.id
        value: @props.value
        onChange: @props.onChange

12 个答案:

答案 0 :(得分:243)

如果我理解正确,你有一个父组件将start_time传递给ModalBody组件,并将其分配给自己的状态?并且您希望从父级更新该时间,而不是子组件。

React has some tips on dealing with this scenario.(请注意,这是一篇旧文章,此后已从网上删除。以下是指向当前doc on component props的链接。

  

使用道具在getInitialState中生成状态通常会导致“真实来源”的重复,即真实数据的位置。这是因为getInitialState仅在首次创建组件时调用。

     

尽可能即时计算值,以确保它们以后不会失去同步并导致维护问题。

基本上,无论何时将父级props分配给子级state,都不会在prop update上调用render方法。您必须使用componentWillReceiveProps方法手动调用它。

componentWillReceiveProps(nextProps) {
  // You don't have to do this check first, but it can help prevent an unneeded render
  if (nextProps.startTime !== this.state.startTime) {
    this.setState({ startTime: nextProps.startTime });
  }
}

答案 1 :(得分:48)

显然事情正在发生变化.... getDerivedStateFromProps()现在是首选功能。

time_point

(above code by danburzo @ github )

答案 2 :(得分:16)

也有componentDidUpdate个可用。

功能签名:

componentDidUpdate(prevProps, prevState, snapshot)

使用此机会在组件更新后在DOM上进行操作。不会在初始render上被呼叫。

请参见You Probably Don't Need Derived State文章,该文章描述了componentDidUpdategetDerivedStateFromProps的反模式。我发现它非常有用。

答案 3 :(得分:14)

componentWillReceiveProps被弃用,因为使用它“经常会导致错误和不一致”。

如果外面有什么变化,请考虑使用key 完全重置子组件。

在子组件上提供key道具可以确保只要key的值从外部更改,该组件就会重新呈现。例如,

<EmailInput
  defaultEmail={this.props.user.email}
  key={this.props.user.id}
/>

关于其性能:

  

虽然这听起来可能很慢,但性能差异通常并不明显。如果组件具有繁重的逻辑并在更新上运行,因为绕过该子树的差异,使用键甚至可以更快。

>

答案 4 :(得分:2)

从react文档中:https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html

  

道具更改时的擦除状态是反模式

从React 16开始,不推荐使用componentWillReceiveProps。从react文档中,这种情况下推荐的方法是使用

  1. 完全受控的组件:ParentComponent的{​​{1}}将拥有ModalBody状态。在这种情况下,这不是我的首选方法,因为我认为模态应该拥有该状态。
  2. 完全不受控制的组件,带有一个键:这是我的首选方法。 react文档中的一个示例:https://codesandbox.io/s/6v1znlxyxn。您将完全拥有start_time的{​​{1}}状态,并像已完成操作一样使用start_time。要重置ModalBody状态,只需从getInitialState
  3. 更改密钥即可。

答案 5 :(得分:2)

执行此操作的新钩子方法是使用useEffect而不是componentWillReceiveProps旧方法:

componentWillReceiveProps(nextProps) {
  // You don't have to do this check first, but it can help prevent an unneeded render
  if (nextProps.startTime !== this.state.startTime) {
    this.setState({ startTime: nextProps.startTime });
  }
}

在功能挂钩驱动的组件中成为以下组件:

// store the startTime prop in local state
const [startTime, setStartTime] = useState(props.startTime)
// 
useEffect(() => {
  if (props.startTime !== startTime) {
    setStartTime(props.startTime);
  }
}, [props.startTime]);

我们使用setState设置状态,使用useEffect检查对指定道具的更改,并采取行动以在道具更改时更新状态。

答案 6 :(得分:0)

从他们的文档中可以很明显地看出:

If you used componentWillReceiveProps for re-computing some data only when a prop changes, use a memoization helper instead.

使用:https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#what-about-memoization

答案 7 :(得分:0)

You Probably Don't Need Derived State

1。从父级设置密钥

  

当键改变时,React会创建一个新的组件实例,而不是   而不是更新当前的。键通常用于动态列表   但在这里也很有用。

2。使用getDerivedStateFromProps / componentWillReceiveProps

  

如果密钥由于某种原因无法使用(可能是组件初始化非常昂贵)

通过使用getDerivedStateFromProps,您可以重置状态的任何部分,但似乎  目前(v16.7)有点越野车!请参阅the link above的用法

答案 8 :(得分:0)

我认为使用ref对我来说是安全的,不需要关心上面的某些方法。

class Company extends XComponent {
    constructor(props) {
        super(props);
        this.data = {};
    }
    fetchData(data) {
        this.resetState(data);
    }
    render() {
        return (
            <Input ref={c => this.data['name'] = c} type="text" className="form-control" />
        );
    }
}
class XComponent extends Component {
    resetState(obj) {
        for (var property in obj) {
            if (obj.hasOwnProperty(property) && typeof this.data[property] !== 'undefined') {
                if ( obj[property] !== this.data[property].state.value )
                    this.data[property].setState({value: obj[property]});
                else continue;
            }
            continue;
        }
    }
}

答案 9 :(得分:0)

使用记事

op的状态派生是对道具的直接操纵,不需要真正的派生。换句话说,如果您有可以直接使用或转换的道具,则无需将道具存储在状态中

鉴于start_time的状态值只是道具start_time.format("HH:mm"),道具中包含的信息本身已经足以更新组件。

但是,如果您确实只希望在更改道具时调用格式,则根据最新文档进行此操作的正确方法是通过Memoize: https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#what-about-memoization

答案 10 :(得分:0)

// store the startTime prop in local state
const [startTime, setStartTime] = useState(props.startTime)
// 
useEffect(() => {
  if (props.startTime !== startTime) {
    setStartTime(props.startTime);
  }
}, [props.startTime]);

此方法可以迁移到类组件吗?

答案 11 :(得分:0)

表单的数据源必须基于用户输入,在用户输入的情况下,任何导致子组件更新的情况,都会触发componentWillReceiveProps或getDerivedStateFromProps操作,此时,比较后的值肯定不相等,执行setState后,用户输入的值会发生变化,这不是一个错误吗?