1
votes

I am passing props from Parent component into Child's state but They are out of sync.

What I tried:

  1. State Updates May Be Asynchronous, I have taken care of that using a call back instead of returning an object.
  2. Objects are passed by reference, but the prop i used is a string.

I am using React 16 and es6 syntax

class Parent extends React.Component {
  state = {
   isHidden: false
  }
  render() {
   console.log('PROPS from PARENT', this.state.isHidden)
   return <div>
     <Child isOpen={this.state.isHidden} />
     <button onClick={this.toggleModal}>Toggle Modal</button>
    </div>
   }
  toggleModal = () => this.setState(state => ({isHidden: !state.isHidden}))
 }

class Child extends React.Component {
  state = {
   isHidden: this.props.isOpen
  }
  render() {
    console.log('STATE of CHILD:',this.state.isHidden)
    return <p hidden={this.state.isHidden}>Hidden:{this.state.isHidden}</p>
  }
}
ReactDOM.render(<Parent/>, document.getElementById('app'));

Here a codepen PEN - notice the redered element is supposed to be hidden based on the state(state depends on props from parent)

console shows my props and state are not equal

4
Possible Duplicate of Setting state based on propsShubham Khatri
Please note that setting state which is directly derivable from props is often not right way to go since you can always use the value directly from props, You should do it only in cases where the prop needs to change from props only after a particular actionShubham Khatri
Thats exactly what my code does, set state to props, then set sate based on a particular action @ShubhamKhatri thanks.q3e
By a particular action, I meant an action that triggers update from child to parent not the other way around. For instance, you might want to prepopulate a form data based on props, then edit those and on a submit action send the data back to parent.Shubham Khatri
Am I not triggering an action from the child? I don't understandq3e

4 Answers

2
votes

Use componentWillReceiveProps which call when changes in props occur.

class Child extends React.Component {
    state = {
        isHidden: this.props.isOpen
    }
    componentWillReceiveProps(props) {

        if (props.isOpen != this.state.isHidden)
            this.setState({
                isHidden: props.isOpen
            })

    }
    render() {
        console.log('STATE of CHILD:', this.state.isHidden)
        return <p hidden = {
            this.state.isHidden
        } > Hidden: {
            this.state.isHidden
        } < /p>
    }
}
2
votes

If you remove the state definition from the child, which is not needed, and use only the props that is passed from the parent, I believe that the behaviour of the child will make sense.

If you want to use state in the child, the constructor setting is not enough, you need the set the child state when props changes.

Console.log is asynchronous, so you cannot rely on it here.

1
votes

Your Child's state does not change with prop change since component does not know anything about state change in your constructor. This is a common pitfall when you depend on your props to construct your local state. You can use componentWillReceiveProps as shown in @Nishant Dixit's answer. But, starting with React 16.3 we have getDerivedStateFromProps function (lifecylce method) for this.

static getDerivedStateFromProps( props, state) {
    if( props.isOpen === state.isHidden) {
      return null;
    }
    return {
      isHidden: props.isOpen,
    }
  }

Here, we are comparing our prop and state, if there is a change we are returning desired state. No need to use this.setState.

Related API change blog post including async rendering: https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html

1
votes

Although the other answers will make the code work, there is actually a more elegant solution :)

Your child component does not need any state as the state is managed by the Parent (which manages the isHidden property and passes it to the child). So the child component should only care about props.

Try writing the component like this and I believe it should work:

class Child extends React.Component {
  render() {
    return <p hidden={this.props.isHidden}>Hidden:{this.props.isHidden}</p>
  }
}

Dan Abramov who works on the React team tweeted about this problem - essentially saying that you should think hard about whether you can just use props before using state in a component https://twitter.com/dan_abramov/status/979520339968516097?lang=en