Navigate back to the homepage

freeCodeCamp Pomodoro Clock 01: React Functional Components and Local State Using Hooks

Aryan Jabbari
February 2nd, 2020 · 4 min read

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:

1// /src/components/Break.jsx
2import React from "react";
3
4const Break = () => {
5 return <div></div>;
6};
7
8export 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:

1// /src/App.js
2import React from "react";
3import "./App.css";
4import Break from "./components/Break"; // 👈 import Break here
5
6function App() {
7 return (
8 <div className="App">
9 <Break />
10 </div>
11 );
12}
13
14export 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):

1const [
2 favoriteColor,
3 setfavoriteColor
4] = 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:

1// /src/components/Break.jsx
2import React, {
3 useState
4} from "react";
5
6const Break = () => {
7 const [
8 breakLengthInSeconds,
9 setBreakLengthInSeconds
10 ] = useState(300);
11 return (
12 <div>
13 <p id="break-label">Break</p>
14 <p id="break-length">
15 {breakLengthInSeconds}
16 </p>
17 </div>
18 );
19};
20
21export 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:

1const decrementBreakLengthByOneMinute = () => {
2 const newBreakLengthInSeconds =
3 breakLengthInSeconds - 60;
4 if (
5 newBreakLengthInSeconds < 0
6 ) {
7 setBreakLengthInSeconds(0);
8 } else {
9 setBreakLengthInSeconds(
10 newBreakLengthInSeconds
11 );
12 }
13};
14const incrementBreakLengthByOneMinute = () =>
15 setBreakLengthInSeconds(
16 breakLengthInSeconds + 60
17 );

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

1<button onClick={activateLasers}>
2 Activate Lasers
3</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:

1// src/components/Break.jsx
2import React, {
3 useState
4} from "react";
5
6const Break = () => {
7 const [
8 breakLengthInSeconds,
9 setBreakLengthInSeconds
10 ] = useState(300);
11
12 const decrementBreakLengthByOneMinute = () => {
13 const newBreakLengthInSeconds =
14 breakLengthInSeconds - 60;
15 if (
16 newBreakLengthInSeconds < 0
17 ) {
18 setBreakLengthInSeconds(0);
19 } else {
20 setBreakLengthInSeconds(
21 newBreakLengthInSeconds
22 );
23 }
24 };
25 const incrementBreakLengthByOneMinute = () =>
26 setBreakLengthInSeconds(
27 breakLengthInSeconds + 60
28 );
29 return (
30 <div>
31 <p id="break-label">Break</p>
32 <p id="break-length">
33 {breakLengthInSeconds}
34 </p>
35 <button
36 id="break-increment"
37 onClick={
38 incrementBreakLengthByOneMinute
39 }
40 >
41 +
42 </button>
43 <button
44 id="break-decrement"
45 onClick={
46 decrementBreakLengthByOneMinute
47 }
48 >
49 -
50 </button>
51 </div>
52 );
53};
54
55export 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:

1npm 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.

1// /src/components/Break.jsx
2
3import moment from "moment";
4import React, {
5 useState
6} from "react";
7
8const Break = () => {
9 const [
10 breakLengthInSeconds,
11 setBreakLengthInSeconds
12 ] = useState(300);
13
14 const decrementBreakLengthByOneMinute = () => {
15 const newBreakLengthInSeconds =
16 breakLengthInSeconds - 60;
17 if (
18 newBreakLengthInSeconds < 0
19 ) {
20 setBreakLengthInSeconds(0);
21 } else {
22 setBreakLengthInSeconds(
23 newBreakLengthInSeconds
24 );
25 }
26 };
27 const incrementBreakLengthByOneMinute = () =>
28 setBreakLengthInSeconds(
29 breakLengthInSeconds + 60
30 );
31
32 const breakLengthInMinutes = moment
33 .duration(
34 breakLengthInSeconds,
35 "s"
36 )
37 .minutes(); // the seconds to minutes conversion is HERE!
38 return (
39 <div>
40 <p id="break-label">Break</p>
41 {/* Note the variable change below */}
42 <p id="break-length">
43 {breakLengthInMinutes}
44 </p>
45 <button
46 id="break-increment"
47 onClick={
48 incrementBreakLengthByOneMinute
49 }
50 >
51 +
52 </button>
53 <button
54 id="break-decrement"
55 onClick={
56 decrementBreakLengthByOneMinute
57 }
58 >
59 -
60 </button>
61 </div>
62 );
63};
64
65export 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

1import React from "react";
2import "./App.css";
3import Break from "./components/Break";
4import Session from "./components/Session";
5
6function App() {
7 return (
8 <div className="App">
9 <Break />
10 <Session />
11 </div>
12 );
13}
14
15export default App;

Session.jsx

1import moment from "moment";
2import React, {
3 useState
4} from "react";
5
6const Session = () => {
7 const [
8 sessionLengthInSeconds,
9 setSessionLengthInSeconds
10 ] = useState(60 * 25);
11
12 const decrementSessionLengthByOneMinute = () => {
13 const newSessionLengthInSeconds =
14 sessionLengthInSeconds - 60;
15 if (
16 newSessionLengthInSeconds < 0
17 ) {
18 setSessionLengthInSeconds(0);
19 } else {
20 setSessionLengthInSeconds(
21 newSessionLengthInSeconds
22 );
23 }
24 };
25 const incrementSessionLengthByOneMinute = () =>
26 setSessionLengthInSeconds(
27 sessionLengthInSeconds + 60
28 );
29
30 const sessionLengthInMinutes = moment
31 .duration(
32 sessionLengthInSeconds,
33 "s"
34 )
35 .minutes();
36 return (
37 <div>
38 <p id="session-label">
39 Session
40 </p>
41 <p id="session-length">
42 {sessionLengthInMinutes}
43 </p>
44 <button
45 id="session-increment"
46 onClick={
47 incrementSessionLengthByOneMinute
48 }
49 >
50 +
51 </button>
52 <button
53 id="session-decrement"
54 onClick={
55 decrementSessionLengthByOneMinute
56 }
57 >
58 -
59 </button>
60 </div>
61 );
62};
63
64export 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.

📧 Join my email list and get notified about new content

Stay updated with all my blog posts and future randomness!

More articles from The WebDev Coach

freeCodeCamp Pomodoro Clock 00: create-react-app Development Environment

freeCodeCamp Pomodoro Clock 00: create-react-app Development Environment Congratulations on getting this far in your coding journey! You…

January 19th, 2020 · 3 min read

User Registration with a NestJS GraphQL Server and Prisma

User Registration with a NestJS GraphQL Server and Prisma Phew, it’s been a while. Let’s get back to creating our NestJS server leveraging…

January 13th, 2020 · 3 min read
© 2018–2020 The WebDev Coach
Link to $https://twitter.com/aryanjabbariLink to $https://www.youtube.com/c/thewebdevcoachLink to $https://dev.to/aryanjnycLink to $https://github.com/AryanJ-NYCLink to $https://www.linkedin.com/in/aryanjabbariLink to $https://www.instagram.com/thewebdevcoach/