DEV Community ๐Ÿ‘ฉโ€๐Ÿ’ป๐Ÿ‘จโ€๐Ÿ’ป

Cover image for freeCodeCamp Pomodoro Clock 01: React Functional Components and Local State
Aryan J
Aryan J

Posted on • Updated on • Originally published at thewebdevcoach.com

freeCodeCamp Pomodoro Clock 01: React Functional Components and Local State

Welcome back!

This tutorial is the second installment of a tutorial series where I cover the freeCodeCamp Pomodoro Clock project. Iโ€™ll be following the spec pretty closely including passing 100% of the tests in the freeCodeCamp test suite.

If you missed the last installment, feel free to read at freeCodeCamp Pomodoro Clock 00: create-react-app Development Environment.

As you read this blog post, donโ€™t forget to stop and try it yourself before I reveal the correct code. Youโ€™ll learn a lot more that way!

For those of you who learn better via video, Iโ€™ve also created a video walking through these same steps:

Goals

By the end of this tutorial, you should:

  • understand how to create a new functional component
  • understand how to read and set state in a functional component
  • understand how to bind a function to a buttonโ€™s click event handler
  • how to convert seconds into minutes using Moment.js

To achieve these goals, weโ€™ll create three components:

  • a Break component that tracks the break time
  • a Session component that tracks the session time, and
  • a TimeLeft component that will display the time left in the current session
    • this component will share the data set by the Session component (and, in a later tutorial, the Break component)

Now, start your development server using npm start and letโ€™s get started!

Break Component

Create a New Functional Component

Inside of your /src directory, create a /components directory. Weโ€™ll use this directory to keep our file structure nice and tidy.

Now, inside your /components directory, create a new file: Break.jsx. Initialize the file with functional component boilerplate:

// /src/components/Break.jsx
import React from "react";

const Break = () => {
  return <div></div>;
};

export default Break;

Move the <p id=โ€œbreak-labelโ€>Break</p> line in src/App.js inside the /src/components/Break.jsx <div> element. Finally, import the Break component into your App.js file and render it in between the <div className=โ€œAppโ€> element:

// /src/App.js
import React from "react";
import "./App.css";
import Break from "./components/Break"; // ๐Ÿ‘ˆ import Break here

function App() {
  return (
    <div className="App">
      <Break />
    </div>
  );
}

export default App;

If you did everything correctly and visit http://localhost:3000/, nothing should have changed since last time. The text โ€Breakโ€ should be rendered in the center of your browser.

Initialize Break Length using React State (and useState)

Since weโ€™re starting with break, letโ€™s tackle a freeCodeCamp User Story. Specifically, weโ€™ll tackle: โ€*User Story #5: I can see an element with a corresponding id=โ€œbreak-lengthโ€, which by default (on load) displays a value of 5.โ€.

As per the spec, weโ€™ll render the number of minutes to the user. However, since weโ€™ll need to use seconds when we implement the countdown feature, weโ€™ll store the data as seconds. To store data that can be modified by the user and forces the component to re-render on change (basically, the new state will be rendered In the browser), weโ€™ll use React state. More specifically, weโ€™ll use the React state hook in our Break component.

The syntax for useState() is as follows (weโ€™ll use favoriteColor as an example):

const [
  favoriteColor,
  setfavoriteColor
] = useState("red");

Here, favoriteColor is the actual variable that is initialized to 'red'. We can change the value of favoriteColor by calling setFavoriteColor with a new string: setFavoriteColor(โ€˜blueโ€™).

Letโ€™s add state to the Break component! On the first line inside /src/components/Break.jsx, write: const [breakLengthInSeconds, setBreakLengthInSeconds] = useState(300); (where 300 is 5 minutes in seconds).

Then, render breakLengthInSeconds below the existing <p> tag inside a <p> tag of its own (donโ€™t forget id=โ€œbreak-lengthโ€.to prepare to pass another freeCodeCamp test)!

If you did everything correctly, /src/components/Break.jsx should look like:

// /src/components/Break.jsx
import React, {
  useState
} from "react";

const Break = () => {
  const [
    breakLengthInSeconds,
    setBreakLengthInSeconds
  ] = useState(300);
  return (
    <div>
      <p id="break-label">Break</p>
      <p id="break-length">
        {breakLengthInSeconds}
      </p>
    </div>
  );
};

export default Break;

Youโ€™ll notice the browser renders out โ€300โ€ instead of the requested โ€5โ€. No worries, weโ€™ll fix that later.

Add Plus & Minus Buttons with Click Event Handlers

Letโ€™s start by writing the functions thatโ€™ll be called by the plus and minus buttons, respectively. The plus button should add one minute (60 seconds) to the break length while the minus button does the opposite (without allowing the number of seconds to drop below 0). In Break.jsx (between declaring setBreakLengthInSeconds and returning the JSX), write the following two functions:

const decrementBreakLengthByOneMinute = () => {
  const newBreakLengthInSeconds =
    breakLengthInSeconds - 60;
  if (
    newBreakLengthInSeconds < 0
  ) {
    setBreakLengthInSeconds(0);
  } else {
    setBreakLengthInSeconds(
      newBreakLengthInSeconds
    );
  }
};
const incrementBreakLengthByOneMinute = () =>
  setBreakLengthInSeconds(
    breakLengthInSeconds + 60
  );

To handle events in React, we need to remember to use camel case for event listener attributes in our HTML elements. For example,

<button onClick={activateLasers}>
  Activate Lasers
</button>

Notice the capital โ€Cโ€ here.

In the JSX part of Break.jsx, add plus and minus buttons (with the ids as requested in freeCodeCamp) that call the two functions we wrote above . If you did everything correctly, your Break.jsx should look like this:

// src/components/Break.jsx
import React, {
  useState
} from "react";

const Break = () => {
  const [
    breakLengthInSeconds,
    setBreakLengthInSeconds
  ] = useState(300);

  const decrementBreakLengthByOneMinute = () => {
    const newBreakLengthInSeconds =
      breakLengthInSeconds - 60;
    if (
      newBreakLengthInSeconds < 0
    ) {
      setBreakLengthInSeconds(0);
    } else {
      setBreakLengthInSeconds(
        newBreakLengthInSeconds
      );
    }
  };
  const incrementBreakLengthByOneMinute = () =>
    setBreakLengthInSeconds(
      breakLengthInSeconds + 60
    );
  return (
    <div>
      <p id="break-label">Break</p>
      <p id="break-length">
        {breakLengthInSeconds}
      </p>
      <button
        id="break-increment"
        onClick={
          incrementBreakLengthByOneMinute
        }
      >
        +
      </button>
      <button
        id="break-decrement"
        onClick={
          decrementBreakLengthByOneMinute
        }
      >
        -
      </button>
    </div>
  );
};

export default Break;

Now go back to the running app in your browser. The buttons should add and subtract 60 seconds to your break time.

Converting Seconds to Minutes using Moment.js

Letโ€™s get rid of the โ€300โ€ that is rendered and, instead, render the โ€5โ€ the was requested of us by the freeCodeCamp spec.

Dealing with time is famously difficult. Sure, converting from seconds to minutes is easy enough (just divide by 60, right) but why write the code? Moment.js is an spectacular library that makes dealing with time easy (and weโ€™ll use it later in this project when displaying the time left).

Letโ€™s start by installing moment to our project:

npm install moment

Weโ€™ll use Moment durations to convert from seconds to minutes. To create a duration, the syntax is moment.duration(timeCount, unitOfTime). For example, since our units are in seconds, weโ€™ll create a direction with moment.duration(breakLengthInSeconds, โ€™sโ€™) . To convert that into minutes, just chain a call to .minutes() at the end. Save this to a variable and render out that variable.

// /src/components/Break.jsx

import moment from "moment";
import React, {
  useState
} from "react";

const Break = () => {
  const [
    breakLengthInSeconds,
    setBreakLengthInSeconds
  ] = useState(300);

  const decrementBreakLengthByOneMinute = () => {
    const newBreakLengthInSeconds =
      breakLengthInSeconds - 60;
    if (
      newBreakLengthInSeconds < 0
    ) {
      setBreakLengthInSeconds(0);
    } else {
      setBreakLengthInSeconds(
        newBreakLengthInSeconds
      );
    }
  };
  const incrementBreakLengthByOneMinute = () =>
    setBreakLengthInSeconds(
      breakLengthInSeconds + 60
    );

  const breakLengthInMinutes = moment
    .duration(
      breakLengthInSeconds,
      "s"
    )
    .minutes(); // the seconds to minutes conversion is HERE!
  return (
    <div>
      <p id="break-label">Break</p>
      {/* Note the variable change below */}
      <p id="break-length">
        {breakLengthInMinutes}
      </p>
      <button
        id="break-increment"
        onClick={
          incrementBreakLengthByOneMinute
        }
      >
        +
      </button>
      <button
        id="break-decrement"
        onClick={
          decrementBreakLengthByOneMinute
        }
      >
        -
      </button>
    </div>
  );
};

export default Break;

You should now be passing โ€œUser Story 5โ€ in your freeCodeCamp test suite.

Session Component

The session component will be in a new file (/src/components/Session) is almost identical to the break component with changes to variable and HTML id names (to match those in the freeCodeCamp test suite). Additionally, as per the freeCodeCamp test suite, the value of the initial session length should be equal to 25 minutes.

App.js

import React from "react";
import "./App.css";
import Break from "./components/Break";
import Session from "./components/Session";

function App() {
  return (
    <div className="App">
      <Break />
      <Session />
    </div>
  );
}

export default App;

Session.jsx

import moment from "moment";
import React, {
  useState
} from "react";

const Session = () => {
  const [
    sessionLengthInSeconds,
    setSessionLengthInSeconds
  ] = useState(60 * 25);

  const decrementSessionLengthByOneMinute = () => {
    const newSessionLengthInSeconds =
      sessionLengthInSeconds - 60;
    if (
      newSessionLengthInSeconds < 0
    ) {
      setSessionLengthInSeconds(0);
    } else {
      setSessionLengthInSeconds(
        newSessionLengthInSeconds
      );
    }
  };
  const incrementSessionLengthByOneMinute = () =>
    setSessionLengthInSeconds(
      sessionLengthInSeconds + 60
    );

  const sessionLengthInMinutes = moment
    .duration(
      sessionLengthInSeconds,
      "s"
    )
    .minutes();
  return (
    <div>
      <p id="session-label">
        Session
      </p>
      <p id="session-length">
        {sessionLengthInMinutes}
      </p>
      <button
        id="session-increment"
        onClick={
          incrementSessionLengthByOneMinute
        }
      >
        +
      </button>
      <button
        id="session-decrement"
        onClick={
          decrementSessionLengthByOneMinute
        }
      >
        -
      </button>
    </div>
  );
};

export default Session;

Open up your freeCodeCamp test suite and run the tests. You should now be passing seven tests!

You Made It! ๐Ÿ‘ฉโ€๐Ÿ’ป ๐Ÿ‘

Way to go! You created the first two components needed for the freeCodeCamp Pomodoro Clock.

If you enjoyed this tutorial, follow me at:

If at any point you got stuck in this tutorial, please review the code on GitHub.

If you are interested in the freeCodeCamp Random Quote Machine implementation, please take a look at my videos on YouTube.

Top comments (0)

๐ŸŒš Friends don't let friends browse without dark mode.

Sorry, it's true.