getDerivedStateFromProps, change of state under the influence of changing props getDerivedStateFromProps, change of state under the influence of changing props reactjs reactjs

getDerivedStateFromProps, change of state under the influence of changing props


here:

selectItem = (id) => {    this.setState({        itemId: id    })    this.load(id);}

you call setState(), then 'Item' and 'Details' and 'AnotherItem' call their render method. so you see log for previous 'loadItemId'.

when 'load' method work done. here:

 this.setState({     loadItemId: response.data });

you setState() again, then 'Item' and 'Details' and 'AnotherItem' call their render method again. in this time you see log for new 'loadItemId'.


solution

setState both state in one place. after load method done, instead of:

 this.setState({     loadItemId: response.data });

write:

 this.setState({     itemId: id,     loadItemId: response.data });

and remove:

this.setState({  itemId: id})

from 'selectItem' method.


Need some clarification, but think I can still address this at high level. As suggested in comment above, with the information presented, it does not seem that your component AnotherItem actually needs to maintain state to determine the correct time at which to invoke start() method (although it may need to be stateful for other reasons, as noted below).

It appears the functionality you are trying to achieve (invoke start method at particular time) can be completed solely with a comparison of old/new props by the componentDidUpdate lifecycle method. As provided by the React docs, getDerivedStateFromProps is actually reserved for a few 'rare' cases, none of which I believe are present here. Rather, it seems that you want to call a certain method, perhaps perform some calculation, when new props are received and meet a certain condition (e.g., not equal to old props). That can be achieved by hooking into componentDidUpdate.

class AnotherItem extends Component {   constructor(props) {      super(props);      this.state  = {}   }   start = () => { do something, perform a calculation }  // Invoked when new props are passed  componentDidUpdate(prevProps) {     // Test condition to determine whether to call start() method based on new props,     // (can add other conditionals limit number of calls to start, e.g.,      // compare other properties of loadItemId from prevProps and this.props) .     if (this.props.loadItemId && this.props.loadItemId.completed === true) {         //Possibly store result from start() in state if needed         const result = this.start();      }     }  }    render () {       // Render UI, maybe based on updated state/result of start method if        // needed    );  }}


You are encountering this behaviour because you are changing state of Items component on each click with

this.setState({    itemId: id})

When changing its state, Items component rerenders causing AnotherItem to rerender (because that is child component) with it's previous state which has completed as true (since you've clicked element D before). Then async request completes and another rerender is caused with

this.setState({    loadItemId: response.data});

which initiates another AnotherItem rerender and expected result which is false.

Try removing state change in selectItem and you'll get desired result.

I'd suggest you read this article and try to structure your code differently.

EDITYou can easily fix this with adding loader to your component:

selectItem = (id) => {    this.setState({        itemId: id,        loading: true    })    this.load(id);}load = (id) => {    axios.get        axios({            url: `https://jsonplaceholder.typicode.com/todos/${id}`,            method: "GET"        })        .then(response => {            this.setState({                loading: false,                loadItemId: response.data            });        })        .catch(error => {            console.log(error);        })}render() {    return (        <div >            <ul>                {this.state.items.map((item, index) =>                    <Item                        key={item.id}                        item={item}                        selectItem={this.selectItem}                    />                )                }            </ul>            {this.state.loading ? <span>Loading...</span> : <Details                itemId={this.state.itemId}                loadItemId={this.state.loadItemId}            />}        </div>    )}

This way, you'll rerender your Details component only when you have data fetched and no unnecessary rerenders will occur.