2

I am trying to build a traffic light using ReactJS. I need the traffic light to go in the order red, yellow, green, red, yellow, green, etc. My current code gives me a different pattern. I don't know quite how to work with setInterval on this one and I could really use some help. I looked at a solution to the same problem by CarterTsai on CodePen, but I was neither able to understand most of his code, nor convert it into a stackblitz-friendly format to adapt it to my code. Does anyone have any answers? Any help is appreciated! It would also be great if anyone could provide a solution with two components - one that changes the traffic light state, and another that utilizes that state shows the actual traffic light(that was the requirement for my homework).

Here is a link to view my project on stackblitz: https://stackblitz.com/edit/react-eqkhd7?file=src/style.css

my code in App.js :

import React from 'react';
import './style.css';

export default class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      color1: '',
      color2: '',
      color3: ''
    };

    setInterval(this.changeColor1.bind(this), 2000);
    setInterval(this.changeColor2.bind(this), 4000);
    setInterval(this.changeColor3.bind(this), 6000);
  }

  changeColor1 = () => {
    this.setState({ color1: 'red', color2: '', color3: '' });
  };

  changeColor2 = () => {
    this.setState({ color1: '', color2: 'yellow', color3: '' });
  };

  changeColor3 = () => {
    this.setState({ color1: '', color2: '', color3: 'green' });
  };

  render() {
    return (
      <div>
        <span style={{ backgroundColor: this.state.color1 }} class="circle" />
        <p />
        <span style={{ backgroundColor: this.state.color2 }} class="circle" />
        <p />
        <span style={{ backgroundColor: this.state.color3 }} class="circle" />
      </div>
    );
  }
}

style.css :

h1,
p {
  font-family: Lato;
}

.circle {
  margin: auto;
  height: 100px;
  width: 100px;
  border: 5px black solid;
  border-radius: 50%;
  display: block;
}

index.js :

import React from "react";
import ReactDOM from "react-dom";

import App from "./App";

ReactDOM.render(<App />, document.getElementById("root"));
Vasila_M
  • 53
  • 6

2 Answers2

0

Your problem is that you set 3 different intervals, so all 3 state-changing functions are being called continuously, at different time intervals. In particular, the first one that makes it go red happens every 2 seconds - this will include the times, every 4 seconds when you try to make it yellow, and every 6 seconds, when you try to make it green. This is going to lead to unpredictable results because there's no real way of knowing which function will fire first each time - it will likely depend on "random" factors, or perhaps which browser you're using.

I assume your intention is to have a regular change every 2 seconds, in the order red, yellow, green, etc. To do this, make a single function which checks the current state and updates appropriately - and schedule that every 2 seconds.

This is one of many ways you could achieve this. Leave render as you had it, but replace the other methods by these:

  constructor(props) {
    super(props);
    this.state = {
      color1: 'red',
      color2: '',
      color3: ''
    };

    setInterval(this.changeColor, 2000);
  }

  changeColor1 = () => {
    this.setState({ color1: 'red', color2: '', color3: '' });
  };

  changeColor2 = () => {
    this.setState({ color1: '', color2: 'yellow', color3: '' });
  };

  changeColor3 = () => {
    this.setState({ color1: '', color2: '', color3: 'green' });
  };

  changeColor = () => {
    if (this.state.color1) {
      this.changeColor2();
    } else if (this.state.color2) {
      this.changeColor3();
    } else {
      this.changeColor1();
    }
  };

(It would actually be much neater to have a single state property that holds the current color, but that would mean rewriting render too. I'll leave you to think about whether that would be appropriate for you.)

PS: you don't actually need bind here because your methods are defined as properties using arrow functions. You can use one or the other - bind or arrow functions - but it's pointless using both.

Federico Navarrete
  • 3,069
  • 5
  • 41
  • 76
Robin Zigmond
  • 17,805
  • 2
  • 23
  • 34
  • Hello. This solution still gives the error 'Error in /~/src/App.js (22:17) changeColor2 is not defined'. It just continues to give that error over and over again in the console. – Vasila_M Jul 04 '21 at 11:45
  • not sure where my last comment has gone but I have in fact fixed that, apologies. – Robin Zigmond Jul 04 '21 at 12:20
0

You can do this using setTimeout and call the other traffic light function after the current traffic light execution.

export default class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      color1: '',
      color2: '',
      color3: ''
    };

    setTimeout(this.changeColor1, 2000);
  }

  changeColor1 = () => {
    this.setState({ color1: 'red', color2: '', color3: '' });
    setTimeout(this.changeColor2, 2000);
  };

  changeColor2 = () => {
    this.setState({ color1: '', color2: 'yellow', color3: '' });
    setTimeout(this.changeColor3, 2000);
  };

  changeColor3 = () => {
    this.setState({ color1: '', color2: '', color3: 'green' });
    setTimeout(this.changeColor1, 2000);
  };

Here is the working fiddle

Vivek Bani
  • 3,703
  • 1
  • 9
  • 18