[reactjs] Updating state on props change in React Form

I am having trouble with a React form and managing the state properly. I have a time input field in a form (in a modal). The initial value is set as a state variable in getInitialState, and is passed in from a parent component. This in itself works fine.

The problem comes when I want to update the default start_time value through the parent component. The update itself happens in the parent component through setState start_time: new_time. However in my form, the default start_time value never changes, since it is only defined once in getInitialState.

I have tried to use componentWillUpdate to force a change in state through setState start_time: next_props.start_time, which did actually work, but gave me Uncaught RangeError: Maximum call stack size exceeded errors.

So my question is, what's the correct way of updating state in this case? Am I thinking about this wrong somehow?

Current Code:

@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

This question is related to reactjs react-state

The answer is


I think use ref is safe for me, dont need care about some method above.

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

From react documentation : https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html

Erasing state when props change is an Anti Pattern

Since React 16, componentWillReceiveProps is deprecated. From react documentation, the recommended approach in this case is use

  1. Fully controlled component: the ParentComponent of the ModalBody will own the start_time state. This is not my prefer approach in this case since i think the modal should own this state.
  2. Fully uncontrolled component with a key: this is my prefer approach. An example from react documentation : https://codesandbox.io/s/6v1znlxyxn . You would fully own the start_time state from your ModalBody and use getInitialState just like you have already done. To reset the start_time state, you simply change the key from the ParentComponent

Use Memoize

The op's derivation of state is a direct manipulation of props, with no true derivation needed. In other words, if you have a prop which can be utilized or transformed directly there is no need to store the prop on state.

Given that the state value of start_time is simply the prop start_time.format("HH:mm"), the information contained in the prop is already in itself sufficient for updating the component.

However if you did want to only call format on a prop change, the correct way to do this per latest documentation would be via Memoize: https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#what-about-memoization


The new hooks way of doing this is to use useEffect instead of componentWillReceiveProps the old way:

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

becomes the following in a functional hooks driven component:

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

we set the state using setState, using useEffect we check for changes to the specified prop, and take the action to update the state on change of the prop.


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

Can this method be migrated to class components?


componentWillReceiveProps is being deprecated because using it "often leads to bugs and inconsistencies".

If something changes from the outside, consider resetting the child component entirely with key.

Providing a key prop to the child component makes sure that whenever the value of key changes from the outside, this component is re-rendered. E.g.,

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

On its performance:

While this may sound slow, the performance difference is usually insignificant. Using a key can even be faster if the components have heavy logic that runs on updates since diffing gets bypassed for that subtree.


There is also componentDidUpdate available.

Function signatur:

componentDidUpdate(prevProps, prevState, snapshot)

Use this as an opportunity to operate on the DOM when the component has been updated. Doesn't get called on initial render.

See You Probably Don't Need Derived State Article, which describes Anti-Pattern for both componentDidUpdate and getDerivedStateFromProps. I found it very useful.


It's quite clearly from their docs:

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

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


You Probably Don't Need Derived State

1. Set a key from the parent

When a key changes, React will create a new component instance rather than update the current one. Keys are usually used for dynamic lists but are also useful here.

2. Use getDerivedStateFromProps / componentWillReceiveProps

If key doesn’t work for some reason (perhaps the component is very expensive to initialize)

By using getDerivedStateFromProps you can reset any part of state but it seems a little buggy at this time (v16.7)!, see the link above for the usage


Apparently things are changing.... getDerivedStateFromProps() is now the preferred function.

_x000D_
_x000D_
class Component extends React.Component {_x000D_
  static getDerivedStateFromProps(props, current_state) {_x000D_
    if (current_state.value !== props.value) {_x000D_
      return {_x000D_
        value: props.value,_x000D_
        computed_prop: heavy_computation(props.value)_x000D_
      }_x000D_
    }_x000D_
    return null_x000D_
  }_x000D_
}
_x000D_
_x000D_
_x000D_

(above code by danburzo @ github )


Examples related to reactjs

Error: Node Sass version 5.0.0 is incompatible with ^4.0.0 TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument must be of type string. Received type undefined raised when starting react app Template not provided using create-react-app How to resolve the error on 'react-native start' Element implicitly has an 'any' type because expression of type 'string' can't be used to index Invalid hook call. Hooks can only be called inside of the body of a function component How to style components using makeStyles and still have lifecycle methods in Material UI? React Hook "useState" is called in function "app" which is neither a React function component or a custom React Hook function How to fix missing dependency warning when using useEffect React Hook? Unable to load script.Make sure you are either running a Metro server or that your bundle 'index.android.bundle' is packaged correctly for release

Examples related to react-state

What is useState() in React? React js change child component's state from parent component React - how to pass state to another component How do I access store state in React Redux? Understanding React-Redux and mapStateToProps() React - changing an uncontrolled input React with ES7: Uncaught TypeError: Cannot read property 'state' of undefined react change class name on state change Clearing state es6 React Updating state on props change in React Form