miga89
miga89

Reputation: 438

React.JS: Sequence of timed state changes

I have a simple web app with one parent component and three child components. The children are just div boxes that can change their background color from white to red based on their property active. The animateChild() function can be used to turn a specific child red and then white again after 1s.

My goal is to "play" an arbitrary sequence of child animations with a break of 1s between each animation. This should happen in the animateSequence() function.

My initial idea to accomplish that, was to use a for loop and call animateChild(childNum) in a window.setTimeout() at each iteration. However, this didn't work. I guess because setState() is asynchronous and several state changes get merged together.

What is the best way to accomplish a sequence (e.g. Child 1, Child 2, Child 3) of animations with a break time of 1s between each animation? Thank you very much

Javascript Code:

class Child extends React.Component {

  render() {
    var childStyle = "basic"; 
    if (this.props.active){
      childStyle += " active";
    }
    return (
        <div className={childStyle}></div>
    );
  }
}


class Parent extends React.Component {
  constructor(){
    super();
    this.state = {
      firstChildActive:false,
      secondChildActive:false,
      thirdChildActive:false,
    };
    this.animateChild = this.animateChild.bind(this);

  }


  animateChild(childNum){
    var childDict = ["firstChildActive","secondChildActive","thirdChildActive"];
    this.setState({[childDict[childNum]]: true}, function() {

        window.setTimeout(() => {
        this.setState({[childDict[childNum]]: false});

        },1000);
    });
  }

  animateSequence(){
    // animate child 2, then child 1, then child 3, then child 1, etc.
  }

  render() {
    return (
      <div className ="container">
      <Child active={this.state.firstChildActive}/>
      <Child active={this.state.secondChildActive}/>
      <Child active={this.state.thirdChildActive}/>
      </div>
    );
  }
}


ReactDOM.render(<Parent />,document.getElementById('app'));

CSS Code:

.container{
  display:flex;
  width:100%;
  justify-content: space-between;

}

.basic{
  margin: 10px;
  width: 100px;
  height:100px;
  border: 2px solid black;
}

.active{
  background: red;
}

Codepen: https://codepen.io/miga89/full/owgZea/

Upvotes: 0

Views: 401

Answers (3)

Gorr1995
Gorr1995

Reputation: 258

if you want do that without button use componentDidMount method in prototype:

 componentDidMount(){
  this.animateSequence();
}
  render() {
    return (
      <div className ="container">        
      <Child isActive={this.state.firstChildActive }/>
      <Child isActive={this.state.secondChildActive } />
      <Child isActive={this.state.thirdChildActive }/>
      </div>
    );
  }

Upvotes: 0

Win
Win

Reputation: 5584

I've also converted your Child Component into a Stateless Component, the solution was just use a simple setInterval.

https://codepen.io/anon/pen/LLEyVo

const Child = ({ isActive }) => {
    var childStyle = "basic"; 
    if ( isActive ) childStyle += " active";
    return <div className={ childStyle }></div>
}

class Parent extends React.Component {
  constructor(props){
    super(props);
    this.state = {
      firstChildActive  : false,
      secondChildActive : false,
      thirdChildActive  : false,
    };
  }

  animateSequence(){

    const TOTAL = 3;
    const CHILD_DICT = ["firstChildActive","secondChildActive","thirdChildActive"];

    let run = 0;

    var setActive = setInterval( () => {
      this.setState({ [CHILD_DICT[run]]: true });
      run = run + 1;
      if( run === TOTAL ) {
        clearInterval(setActive);
      }
    }, 1000)

  }

  render() {
    return (
      <div className ="container">
        <button onClick={ () => this.animateSequence() }>Run Sequence</button>
      <Child isActive={this.state.firstChildActive }/>
      <Child isActive={this.state.secondChildActive } />
      <Child isActive={this.state.thirdChildActive }/>
      </div>
    );
  }
}


ReactDOM.render(<Parent />,document.getElementById('app'));

Upvotes: 1

Evan Sebastian
Evan Sebastian

Reputation: 1744

Your this context is invalid in the callback of first setState.

var childDict = ["firstChildActive","secondChildActive","thirdChildActive"];
this.setState({[childDict[childNum]]: true}, function() {

Should be

var childDict = ["firstChildActive","secondChildActive","thirdChildActive"];
this.setState({[childDict[childNum]]: true}, () => {

Upvotes: 0

Related Questions