Search code examples
reactjsfinite-state-automaton

Why does my screen not update when I click my button the first time, but works perfectly fine afterwards?


This is my first time making a finite state Automata. I tried making a stop light kind of program where if you click the button, the light changes once, starting at green, then to yellow if clicked again, then to red, before looping again. I managed to make it work, except for one small bug. It needs to be clicked twice before the screen updates, and I don't really know how to fix it.

I noticed on my console that the currentLightState changes when I click on it, but reverts back to the previous color on the first click. I figured out that it's because it is not in sync with my state. However, when I attempt to put currentLightState inside the class, and assign this.state.light, currentLightState becomes undefined. I tried adding a .bind(this) at the end of this.state.light, but I just get another error saying it's not a function. Any suggestions?

I didn't post my update function or my onHandleClick function since it doesn't directly deal with currentLightState.

const lightMachine = {
  green:{
    LIGHT: 'yellow'
  },
  yellow:{
    LIGHT: 'red'
  },
  red:{
    LIGHT: 'green'
  }
}

// current location of currentLightState
let currentLightState = 'green';
class App extends React.Component{
    constructor(props){
        super(props);
        this.state = {
          light: 'green'    //initial value
        };
      }
    transition(state, action){
      // This is where my currentLightState messes up the first run 
      currentLightState = this.state.light

      const nextLightState =  lightMachine[currentLightState][action]
      this.setState({light: nextLightState}) 
    }
    render(){

        return(
            <div>
              <button onClick={this.onHandleClick.bind(this)}>
                change the Light!
              </button>
                {currentLightState}
            </div>
            );
        }
    }

EDIT: Here is my onHandleClick function :)

    onHandleClick(){
      this.update(currentLightState)

    };

also, I think I solved my problem by just substituting the currentLightState in the render function with this.state.light .

Idk if that is a legitimate fix or not, but it seems to work currently.

it would be nice though if someone can still answer why when you put the currentLightState inside the class, and assign state.light to it, it becomes undefined. It would help expand my React knowledge :)


Solution

  • Welcome to the boards Jr194!

    On topic, I think it will be beneficial to keep currentLight and lightMachine as values managed by your component state. This helps ensure all your logic is within the same context, avoiding weird errors like "blah blah is not defined."

    Second, you seem to have defined a few functions that can really be narrowed down to one. update, onHandleClick and transition all seem to be trying to do the same thing.

    Lastly, consider reorganizing the code in your lightMachine to be a bit more intuitive so that you can quickly navigate to the next light. You already know what the current color is in the parent key, is their really a need for having its object contain another key with the same value?

    Consider the following code:

    class App extends React.Component {
      state = {
        currentLight: "green",
        lightMachine: {
          green: {
            nextLight: "yellow"
          },
          yellow: {
            nextLight: "red"
          },
          red: {
            nextLight: "green"
          }
        }
      };
    
      transition = () => {
        const currentLight = this.state.currentLight;
        const nextLight = this.state.lightColors[currentLight].nextLight;
        this.setState({
          currentLight: nextLight
        });
      };
      render() {
        return (
          <div>
            <button onClick={this.transition}>Click Me</button>
            <div>{this.state.currentLight}</div>
          </div>
        );
      }
    }
    

    This should let you quickly change lights as expected. And here's the sandbox: https://codesandbox.io/s/4x08ovyjp7

    Let me know if you have any questions :)

    Also regarding your question about why currentLightState gives you an error in your render when you put it inside the class. This is not a React issue, it's a JavaScript issue.

    Let's go through some examples:

    const test= "hello"
    class Example extends React.Component{
       state = {
           greeting: "woof"
       }
       render(){
         <div>{test}</div>
       }
    }
    

    As you know, this won't give us any error. We are tapping into the variable that's defined outside our class and that's completely fine.

    Now let's see look at this

    class Example extends React.Component{
       state = {
           greeting: "woof"
       }
    
       test = this.state.greeting
    
       render(){
         <div>{test}</div>
       }
    }
    

    This gives us an error. Why, because in your render method, you are treating test like it's a variable, when it is not. In a class object like the one you have written, currentLightState and now test are treated as properties, not variables. In order to get access to a property inside a class, you need to make use of the "this" keyword, something you already have been doing with this.update, this.handleOnClick and etc, which are also properties.

    Now we know this code will work.

    class Example extends React.Component{
       state = {
           greeting: "woof"
       }
    
       test = this.state.greeting
    
       render(){
         <div>{this.test}</div>
       }
    }