React Child Component Not Updating After Parent State Change

Reactjs

Reactjs Problem Overview


I'm attempting to make a nice ApiWrapper component to populate data in various child components. From everything I've read, this should work: https://jsfiddle.net/vinniejames/m1mesp6z/1/

class ApiWrapper extends React.Component {

  constructor(props) {
    super(props);

    this.state = {
      response: {
        "title": 'nothing fetched yet'
      }
    };
  }

  componentDidMount() {
    this._makeApiCall(this.props.endpoint);
  }

  _makeApiCall(endpoint) {
    fetch(endpoint).then(function(response) {
      this.setState({
        response: response
      });
    }.bind(this))
  }

  render() {
    return <Child data = {
      this.state.response
    }
    />;
  }
}

class Child extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      data: props.data
    };
  }

  render() {
    console.log(this.state.data, 'new data');
    return ( < span > {
      this.state.data.title
    } < /span>);
  };
}

var element = < ApiWrapper endpoint = "https://jsonplaceholder.typicode.com/posts/1" / > ;

ReactDOM.render(
  element,
  document.getElementById('container')
);

But for some reason, it seems the child component is not updating when the parent state changes.

Am I missing something here?

Reactjs Solutions


Solution 1 - Reactjs

There are two issues with your code.

Your child component's initial state is set from props.

this.state = {
  data: props.data
};

Quoting from this SO Answer:

> Passing the intial state to a component as a prop is an anti-pattern > because the getInitialState (in our case the constuctor) method is only called the first time the > component renders. Never more. Meaning that, if you re-render that > component passing a different value as a prop, the component > will not react accordingly, because the component will keep the state > from the first time it was rendered. It's very error prone.

So if you can't avoid such a situation the ideal solution is to use the method componentWillReceiveProps to listen for new props.

Adding the below code to your child component will solve your problem with Child component re-rendering.

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

The second issue is with the fetch.

_makeApiCall(endpoint) {
  fetch(endpoint)
  	.then((response) => response.json())   // ----> you missed this part
    .then((response) => this.setState({ response }));
}

And here is a working fiddle: https://jsfiddle.net/o8b04mLy/

Solution 2 - Reactjs

If the above solution has still not solved your problem I'll suggest you see once how you're changing the state, if you're not returning a new object then sometimes react sees no difference in the new previous and the changed state, it's a good practice to always pass a new object when changing the state, seeing the new object react will definitely re-render all the components needing that have access to that changed state.

For example: -

Here I'll change one property of an array of objects in my state, look at how I spread all the data in a new object. Also, the code below might look a bit alien to you, it's a redux reducer function BUT don't worry it's just a method to change the state.

export const addItemToCart = (cartItems,cartItemToBeAdded) => {
        return cartItems.map(item => {
            if(item.id===existingItem.id){
                ++item.quantity;        
            }
            // I can simply return item but instead I spread the item and return a new object
            return {...item} 
        })
    } 

Just make sure you're changing the state with a new object, even if you make a minor change in the state just spread it in a new object and then return, this will trigger rendering in all the appropriate places. Hope this helped. Let me know if I'm wrong somewhere :)

Solution 3 - Reactjs

There are some things you need to change.

When fetch get the response, it is not a json. I was looking for how can I get this json and I discovered this link.

By the other side, you need to think that constructor function is called only once.

So, you need to change the way that you retrieve the data in <Child> component.

Here, I left an example code: https://jsfiddle.net/emq1ztqj/

I hope that helps.

Solution 4 - Reactjs

Accepted answer and componentWillReceiveProps

The componentWillReceiveProps call in accepted answer is deprecated and will be removed from React with version 17 React Docs: UNSAFE_componentWillReceiveProps()

Using derived state logic in React

As the React docs is pointing, using derived state (meaning: a component reflecting a change that is happened in its props) can make your components harder to think, and could be an anti-pattern. React Docs: You Probably Don't Need Derived State

Current solution: getDerivedStateFromProps

If you choose to use derived state, current solution is using getDerivedStateFromProps call as @DiogoSanto said.

> getDerivedStateFromProps is invoked right before calling the render method, both on the initial mount and on subsequent updates. It should return an object to update the state, or null to update nothing. React Docs: static getDerivedStateFromProps()

How to use componentWillReceiveProps

This method can not access instance properties. All it does describing React how to compute new state from a given props. Whenever props are changed, React will call this method and will use the object returned by this method as the new state.

class Child extends React.Component {
    constructor() {
        super(props);
        // nothing changed, assign the state for the 
        // first time to teach its initial shape. 
        // (it will work without this, but will throw 
        // a warning)
        this.state = {
            data: props.data
        };
    }

    componentWillReceiveProps(props) {
        // return the new state as object, do not call .setState()
        return { 
            data: props.data
        };
    }

    render() {
        // nothing changed, will be called after 
        // componentWillReceiveProps returned the new state, 
        // each time props are updated.
        return ( 
            <span>{this.state.data.title}</span>
        );
    }
}

Caution

  • Re-rendering a component according to a change happened in parent component can be annoying for user because of losing the user input on that component.
  • Derived state logic can make components harder to understand, think on. Use wisely.

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
QuestionVinnie JamesView Question on Stackoverflow
Solution 1 - ReactjsyadhuView Answer on Stackoverflow
Solution 2 - ReactjsSarthak SaklechaView Answer on Stackoverflow
Solution 3 - ReactjssaetaView Answer on Stackoverflow
Solution 4 - ReactjsufuktyView Answer on Stackoverflow