Step 10

Let's refactor the App component to a class component:

import { Component } from "react";
import Output from "./Output.js";

class App extends Component {
  constructor(props) {
    super(props);
    this.cycles = [
      "11:44 PM",
      "1:14 AM",
      "2:44 AM",
      "4:14 AM",
      "5:44 AM",
      "7:14 AM",
    ];
  }

  render() {
    return (
      <div>
        <p>If you go to bed NOW, you should wake up at...</p>
        <button>zzz</button>
        <Output cycles={this.cycles} />
      </div>
    );
  }
}

export default App;

Notice the alternative approach to using React.Component class is to directly import the Component class.

Let's add a method to calculate the cycles:

calcCycles() {
  // get current time
  let now = Date.now(); // in milliseconds
  let minute = 60 * 1000; // milliseconds
  let cycle = now;

  // allow 14 minutes to fall sleep
  cycle += 14 * minute;

  // calculate 6 sleep cycles (each 90 minutes)
  for (let i = 0; i < 6; i++) {
    cycle += 90 * minute;

    // update the sleep cycles
    this.cycles[i] = new Date(cycle).toLocaleTimeString([], {
      hour: "2-digit",
      minute: "2-digit",
    });
  }

  // print cycles for sanity check
  console.log(this.cycles);
}

Now we need to attach this method to the zzz button:

<button onClick={this.calcCycles}>zzz</button>

Save the file and reload the app. Then click on zzz button. Notice you will get an error message!

The problem is related to the execution context of calcCycle. The transpiler used in React does not bind the methods to the object of class. To solve this, you can use the bind method as follows:

<button onClick={this.calcCycles.bind(this)}>zzz</button>

If you have a method that you are going to use in multiple places, then you may want to bind it to the class object once, in the class constructor:

constructor(props) {
  super(props);
  this.cycles = [
    "11:44 PM",
    "1:14 AM",
    "2:44 AM",
    "4:14 AM",
    "5:44 AM",
    "7:14 AM",
  ];
  this.calcCycles = this.calcCycles.bind(this);
}

Another alternative is to use arrow functions for method declaration.

calcCycles = () => {
  // get current time
  let now = Date.now(); // in milliseconds
  let minute = 60 * 1000; // milliseconds
  let cycle = now;

  // allow 14 minutes to fall sleep
  cycle += 14 * minute;

  // calculate 6 sleep cycles (each 90 minutes)
  for (let i = 0; i < 6; i++) {
    cycle += 90 * minute;

    // update the sleep cycles
    this.cycles[i] = new Date(cycle).toLocaleTimeString([], {
      hour: "2-digit",
      minute: "2-digit",
    });
  }

  // print cycles for sanity check
  console.log(this.cycles);
}

Arrow functions do not bind their own this, instead, they inherit one from the parent scope, which is called "lexical scoping".

Once you fixed the issue, save the file, reload the app and then click on zzz again!