Updating state with props on React child component

JavascriptReactjs

Javascript Problem Overview


I have a React app, where props from a parent component are passed to a child component and the props then set the state on the child.

After I send an updated value to the parent component, the child component isn't updating the state with the updated props.

How do I get it to update the state on the child component?

My pared-down code:

class Parent extends React.Component {
	constructor (props) {
		super(props);
		this.state = {name: ''}	
	}
	componentDidMount () {
		this.setState({name: this.props.data.name});
	}
	handleUpdate (updatedName) {
		this.setState({name: updatedName});
	}
	render () {
	 	return <Child name={this.state.name} onUpdate={this.handleUpdate.bind(this)} />
	}
}


class Child extends React.Component {
	constructor (props) {
		super(props);
		this.state = {name: ''}	
	}
	componentDidMount () {
		this.setState({name: this.props.name});
	}
	handleChange (e) {
	    this.setState({[e.target.name]: e.target.value});
  	}
	handleUpdate () {
		// ajax call that updates database with updated name and then on success calls onUpdate(updatedName)
	}
	render () {
		console.log(this.props.name); // after update, this logs the updated name
		console.log(this.state.name); // after update, this logs the initial name until I refresh the brower
		return <div>	
		 			{this.state.name}
		 			<input type="text" name="name" value={this.state.name} onChange={this.handleChange} />
		 			<input type="button" value="Update Name" onClick={this.handleUpdate.bind(this)} />
		 		</div>
	}
}

Javascript Solutions


Solution 1 - Javascript

You need to implement componentWillReceiveProps in your child:

componentWillReceiveProps(newProps) {
    this.setState({name: newProps.name});
}

Edit: componentWillReceiveProps is now deprecated and will be removed, but there are alternative suggestions in the docs link above.

Solution 2 - Javascript

Calling setState() in componentWillReceiveProps doesn't cause additional re-render. Receiving props is one render and this.setState would be another render if that were executed within a method like componentDidUpdate. I would recommend doing the this.state.name !== nextProps.name in shouldComponentUpdate so it's always checked for any update.

componentWillReceiveProps(nextProps) {
    this.setState({name: nextProps.name});
}

shouldComponentUpdate(nextProps) {
    return this.state.name !== nextProps.name;
}

Solution 3 - Javascript

It would also be good to check if you even need to update the state, since this will cause a re-render.

componentWillReceiveProps(newProps) {
  if (this.state.name !== newProps.name) {
    this.setState({name: newProps.name});
  }
}

Solution 4 - Javascript

A couple of things. The way you bind the functions on click is unusual to say the least. I would suggest that you either do it on constructor, or use an arrow function instead (this will bind the function to the class automatically).

export default class Parent extends Component {

    constructor (props) {
        super(props);
        this.state = {name: ''} 
    }

    handleUpdate = (updatedName) => {
        this.setState({name: updatedName})
    }

    render () {
        return <Child name={this.state.name} onUpdate={this} />
    }
}

export default class Child extends React.Component {
  constructor(props) {
    super(props);
    this.state = { name: "" };
  }

  componentDidMount() {
    this.setState({ name: this.props.name });
  }

  handleChange = (e) => {
    this.setState({ name: e.target.value });
  }

  handleUpdate() {
    // ajax call that updates database with updated name and then on success calls onUpdate(updatedName)
  }

  render() {
    console.log(this.props.name); // after update, this logs the updated name
    console.log(this.state.name); // after update, this logs the initial name until I refresh the brower
    return (
      <div>
        {this.state.name}
        <input
          type="text"
          name="name"
          value={this.state.name}
          onChange={this.handleChange}
        />
        <input
          type="button"
          value="Update Name"
          onClick={this.handleUpdate}
        />
      </div>
    );
  }
}

Furthermore, I would suggest you to decide whether the props need to be managed/updated from Parent or Child. Should Parent be responsible for handling state, then you should propagate the handleUpdate to the Parent:

//@Parent component
<Child handleUpdate={()=> this.handleUpdate} .../>
//@Child component
handleUpdate = () => {
   this.props.handleUpdate
}

You shouldn't be required to use any other function (in React 16+) for managing props from child to parent and vice versa.

Usually these "bidirectional" cases are structure "smells" and signify that the separation of concerns for each component has been misadjusted or has not been fully figured out yet.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionAdam WhiteView Question on Stackoverflow
Solution 1 - JavascriptBlorgbeardView Answer on Stackoverflow
Solution 2 - JavascriptgoldbulletView Answer on Stackoverflow
Solution 3 - JavascripttylermadisonView Answer on Stackoverflow
Solution 4 - JavascriptMenelaos KotsollarisView Answer on Stackoverflow