Skip to content
Advertisement

How to re-render a component on each of multiple state changes?

I’m still learning JS/React, so it’s likely I’m doing this completely wrong. Any criticism is welcome.

I have a Canvas with a drawning on it. I want to change the color of the drawning multiple times when a button is pressed. To be clear: I want a single click on the button to change the color of the drawning multiple times.

I’ve tried doing this a few different ways, but they are mosly variations of two:

  • When the button is pressed, it calls the method that will change the state multiple times, but React only bothers to render the last state I set. (Which makes sense)

  • Using setTimeout for each setState, but it seems it breaks the method, and the render never changes.

Here is a sample code:

import React from 'react';

class App extends React.Component {
 constructor(props) {
      super(props);
      this.state = {
        color: "#000000",
      }
      this.changeColors = this.changeColors.bind(this);
  }
  
  changeColors() {
    let colors = ["#000000", "#0000FF", "#FF0000", "#00FF00"];
    for (let nextColor in colors) {
      console.log(`Color now ${colors[nextColor]}`);
      // This seems to break it
      //setTimeout(function(){ this.setState({color: colors[nextColor]}); }, 3000);

      // This only renders last state
      this.setState({color: colors[nextColor]});
    }
  }

  render() {
    return (
      <div className="App">
        <h1>Change Colors</h1>
        <MyButton changeColor={this.changeColors}/>
        <MyCanvas color={this.state}/>
      </div>
    );
  }
}

class MyButton extends React.Component {
  render() {
    return (
      <button 
        type="button" 
        className="btn btn-secondary" 
        onClick={() => this.props.changeColor()}>
        Color
      </button>
    );
  }
}

class MyCanvas extends React.Component {
  componentDidMount() {
      this.drawOnCanvas(this.props.color)
  }
  
  componentDidUpdate() {
      this.drawOnCanvas(this.props.color)
  }
  
  drawOnCanvas(color) {
    const ctx = this.refs.canvas.getContext('2d');
    ctx.clearRect(0, 0, 300, 300) 
    ctx.fillStyle=color.color;
    ctx.fillRect(10, 10, 100, 100);
  }
  
  render() {
    return (
      <canvas id="canvas" ref="canvas" width={300} height={300}/>
    );
  }
}

export default App;

What I’m doing wrong and how can I achieve the multiple color changes with react?

Advertisement

Answer

Without setTimeout all the renders will be basically merged into one, this is how React works. However you could try setTimeout with a dynamic timeout.

class App extends React.Component {
 constructor(props) {
      super(props);
      this.state = {
        color: "#000000",
      }
  }
  
  changeColors = () => {
    let colors = ["#000000", "#0000FF", "#FF0000", "#00FF00"];
    colors.forEach((color, i) => {
      setTimeout(() => {
          this.setState({ color });
      }, 500 * i);
    });
  }

  render() {
    return (
      <div className="App" style={{ color: this.state.color }}>
        <h1>Change Colors</h1>
        <button onClick={this.changeColors}>change</button>
      </div>
    );
  }
}

ReactDOM.render(<App />, 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>
User contributions licensed under: CC BY-SA
6 People found this is helpful
Advertisement