0

I have my ClockBlock component where I lifted state keeped in object "timer":

class ClockBlock extends Component {
  constructor(props){
    super(props);
    this.state = { timer: props.timer }; // timer from Redux store
  }

  render(){
    return(
      <div className="clockBlock">
        <Circle timer={this.state.timer} updateTimer={this.updateTimer.bind(this)} />
        <Clock timer={this.state.timer} updateTimer={this.updateTimer.bind(this)} />
        <ClockTerms updateTimer={this.updateTimer.bind(this)} />
      </div>
    )
  }

All the three nested component influence each other via updateTimer function. (except ClockTerms component - it works in one direction). The function is here:

 updateTimer(newDuration){
    const newState = Object.assign( {}, this.state );
    newState.timer.duration = newDuration;
    this.setState( newState );
  }

So, the problem - when I change timer using ClockTerms component I see changes in the Clock component too (via props, apparently). In the same time in the Circle component in shouldComponentUpdate function i'm trying to see the difference between the old props and the new. Here is this function:

 shouldComponentUpdate(nextProps, nextState){
    console.log( this.state.timer );
    console.log( nextState.timer );
    console.log( this.props.timer );
    console.log( nextProps.timer );

    return true;
 }

All the console.log() calls print the same data - new props. Why? And how can I get old props?

PS: i simplified my code above, removing irrelevant from my point of view calculations. Can give whole code, if it is important. I also use Redux here, but it seems to me it isn't engaged here much. Great thanks in advance!

UPDATE: I also get the same picture when place the same shouldComponentUpdate function in ClockBlock (parent) component;

Max Kurtz
  • 448
  • 5
  • 17
  • make `console.log(timer.duration)` to ensure it is update. I believe it's known thing that [`console.log` provides live value](https://stackoverflow.com/questions/11284663/console-log-shows-the-changed-value-of-a-variable-before-the-value-actually-ch) for everything except primitive types. In other words you may see other value that was on the moment of execution. – skyboyer Jan 12 '19 at 21:28
  • I've just tried this: console.log( this.props.timer ); debugger; console.log( this.state.timer ); And nothing changed... new value in this.props.timer even when it is stopped by debugger. – Max Kurtz Jan 12 '19 at 21:35
  • they shouldnt - `nextState.timer.duration` vs. `this.state.timer.duration` are the ones that should differ (in `ClockBlock` at least - in the children it would be `nextProps.timer.duration` and `this.props.timer.duration`). But I also just noticed you're using the timer from a redux store originally - why are you using `setState()` to manipulate the component rather than the redux ``? Don't think it's related just curious. – Deryck Jan 12 '19 at 21:53
  • Deryck and skyboyer - Thank you, I've found the problem. No blame for react or redux, as I thought erlier. The point is that updateTimer fires BEFORE shouldComponentUpdate runs. That's it. So, when you update your lifted state in the parent component from children components, keep in mind that shouldComponentUpdate will get already changed state and props. I didn't know this :-) – Max Kurtz Jan 12 '19 at 21:57

3 Answers3

1

I've solved a similar situation by comparing this.state to nextProps and updating state. I don't think it's a great solution, but didn't come up with anything else jet.

state = {
    dayOff: this.props.myProperty
}

shouldComponentUpdate(nextProps, nextState) {
    const shouldUpdate = this.state.myProperty !== nextProps.myProperty;
    if (shouldUpdate) this.state.myProperty = nextProps.myProperty
    return shouldUpdate;
}
1

You can functionally set state. Because setState is async, when multiple things call setState React uses the most recent properties passed to setState.

Instead you can update state by passing an updater function. This batches all the state updates. When Reacts lifecycle begins to update state it'll process all the pending updater functions in sequence.

This is from the docs describing what happens when setState uses an object.

Subsequent calls will override values from previous calls in the same cycle, so the quantity will only be incremented once. If the next state depends on the current state, we recommend using the updater function form, instead:

this.setState((state) => {
  return {quantity: state.quantity + 1};
});

https://reactjs.org/docs/react-component.html#setstate

Lex
  • 4,749
  • 3
  • 45
  • 66
0

The point is that updateTimer fires BEFORE shouldComponentUpdate runs. That's it. So, when you update your lifted state in the parent component from children components (via passed in props function), keep in mind that shouldComponentUpdate will get already changed state and props.

Max Kurtz
  • 448
  • 5
  • 17