Jibin Thomas
Jibin Thomas

Reputation: 864

Render item from an array after a fixed interval in React

I want to array items on after another. But it is only displaying the last element of the array.

Expected output is displaying "a" after 2 seconds "b" and so on.

class Main extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      role: ["a","b","c"],
      display: "",
    }
  }
  componentDidMount() {
    for (let i of this.state.role) {
      setTimeout(() => {
        this.setState({ display: i})
      }, 2000)
    }
  }

  render() {
    return (
      <div>
          <h3>{this.state.display}</h3>
      </div>
    )
  }
}

ReactDOM.render(<Main />, document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

<div id='root'></div>

Upvotes: 2

Views: 2124

Answers (3)

U Rogel
U Rogel

Reputation: 1941

The problem is that for (x of y) runs directly and all three setTimeouts created almost at once, then you see the result only of the last one cause they have 1-2 ms difference between them. I used for (x in y)instead to have the item index. Then I am using the item index to calculate its timeout based on its index. This is my approach:


import React, { Component } from "react";

export default class Main extends Component {
  constructor(props) {    super(props);
    this.state = {
      role: ["a", "b", "c"],
      display: ""
    };
  }

  componentDidMount() {
    for (let i in this.state.role) {
      setTimeout(() => {
        this.setState({
          display: this.state.role[i],
        });
      }, 2000 * i);
    }
  }

  render() {
    return (
      <>
        <h3>{this.state.display}</h3>
      </>
    );
  }
}

Upvotes: 0

tam.dangc
tam.dangc

Reputation: 2032

You can use way multiple the index to create 3 different timeouts.

class Main extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      role: ["a","b","c"],
      display: "",
    }
  }
  componentDidMount() {
    this.state.role.forEach((item, index) => {
      setTimeout(() => {
        this.setState({ display: item})
      }, 2000*(index+1))
    })
  }

  render() {
    return (
      <div>
          <h3>{this.state.display}</h3>
      </div>
    )
  }
}

ReactDOM.render(<Main />, document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

<div id='root'></div>

Upvotes: 0

Shubham Khatri
Shubham Khatri

Reputation: 281646

The problem is that you are using a loop to create a timeout on componentDidMount. In such a case what would happen is that the loop will complete immedialtely creating 3 timers which resolve at nearly the same 2sec interval from the start and setState then batches all the three state update calls resulting in only the last one to be displayed.

In order to solve this you can make use of setInterval like below

class Main extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      role: ["a","b","c"],
      display: "",
    }
  }
  componentDidMount() {
    var index = 0;
    this.timer = setInterval(() => {
      this.setState({ display: this.state.role[index]});
      index  = (index + 1)%(this.state.role.length);
    }, 2000)
  }

  componentWillUnmounnt() {
    clearInterval(this.timer);
  }
  render() {
    return (
      <React.Fragment>
          <h3>{this.state.display}</h3>
      </React.Fragment>
    )
  }
}

ReactDOM.render(<Main />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"/>

Upvotes: 6

Related Questions