Navigate back to the homepage

freeCodeCamp Pomodoro Clock 02: Lifting State Up and React Props

Aryan Jabbari
February 10th, 2020 · 3 min read

freeCodeCamp Pomodoro Clock 02: Lifting State Up and React Props

This is the third installment of a tutorial series where I cover the freeCodeCamp Pomodoro Clock project. Read the last installment if you missed it.

For those of you who like to learn using video, I’ve also created a video to complement this blog post:

Goals

By the end of this tutorial, you should:

  • understand when to lift state up into a parent component
  • understand how to lift state up into a parent component
  • use props to pass data from a parent component to a child component
  • Format [Moment durations] using moment-duration-format

To achieve these goals, we’ll:

  • Create a TimeLeft component that will display the time left in MM:SS format in the current session or break.

Lifting State Up And React Props

We want to add a component named TimeLeft to our App component that will display the time left in the current session or break. The value of TimeLeft will be initialized to either sessionLength or breakLength, which currently reside in the Session component and Break component, respectively.

Diagram of the current app state
Unfortunately, we cannot share data amongst sibling components. Specifically, in our case, that means that, since Session, Break and TimeLeft components are all children of App (thus considered siblings), TimeLeft cannot currently access sessionLength or breakLength to initialize its value:
However, React does allow data to be passed from a parent component to its children. Specifically, in our case, we can lift sessionLength and breakLength up to the App component (hence the name lift state up) and pass it down to Session, Break and TimeLeft:
Now that we know why we need to lift state up, let’s get to some code.

We’ll begin by lifting the state up and passing sessionLength and breakLength as props to the Session and Break components, respectively. After we make these changes, the app should work just as it did before with our state now moved into the App component.

Let’s start with the Session component. In Session.jsx, cut all the code that uses sessionLengthInSeconds and paste it into App.js (don’t forget to import useState in App.js. That is, the state and its modifiers (increment / decrement):

1// App.js
2import React, { useState } from 'react';
3import './App.css';
4import Break from './components/Break';
5import Session from './components/Session';
6
7function App() {
8 const [sessionLengthInSeconds, setSessionLengthInSeconds] = useState(60 * 25);
9
10 const decrementSessionLengthByOneMinute = () => {
11 const newSessionLengthInSeconds = sessionLengthInSeconds - 60;
12 if (newSessionLengthInSeconds < 0) {
13 setSessionLengthInSeconds(0);
14 } else {
15 setSessionLengthInSeconds(newSessionLengthInSeconds);
16 }
17 };
18 const incrementSessionLengthByOneMinute = () =>
19 setSessionLengthInSeconds(sessionLengthInSeconds + 60);
20
21 return (
22 <div className="App">
23 <Break />
24 <Session />
25 </div>
26 );
27}
28
29export default App;
1// Session.jsx
2import moment from 'moment';
3import React from 'react';
4
5const Session = () => {
6 const sessionLengthInMinutes = moment.duration(sessionLengthInSeconds, 's').minutes();
7 return (
8 <div>
9 <p id="session-label">Session</p>
10 <p id="session-length">{sessionLengthInMinutes}</p>
11 <button id="session-increment" onClick={incrementSessionLengthByOneMinute}>
12 +
13 </button>
14 <button id="session-decrement" onClick={decrementSessionLengthByOneMinute}>
15 -
16 </button>
17 </div>
18 );
19};
20
21export default Session;

You should see red squiggles in Session.jsx at the moment. Our IDE (editor) is telling us that it has no clue what the variables sessionLengthInSeconds, incrementSessionLengthByOneMinute, decrementSessionLengthByOneMinute are. We’ll pass these variables from App.js into Session.jsx using props:

1// App.js
2import React, { useState } from 'react';
3import './App.css';
4import Break from './components/Break';
5import Session from './components/Session';
6
7function App() {
8 const [sessionLengthInSeconds, setSessionLengthInSeconds] = useState(60 * 25);
9
10 const decrementSessionLengthByOneMinute = () => {
11 const newSessionLengthInSeconds = sessionLengthInSeconds - 60;
12 if (newSessionLengthInSeconds < 0) {
13 setSessionLengthInSeconds(0);
14 } else {
15 setSessionLengthInSeconds(newSessionLengthInSeconds);
16 }
17 };
18 const incrementSessionLengthByOneMinute = () =>
19 setSessionLengthInSeconds(sessionLengthInSeconds + 60);
20
21 return (
22 <div className="App">
23 <Break />
24 {/* pass props below! */}
25 <Session
26 sessionLengthInSeconds={sessionLengthInSeconds}
27 incrementSessionLengthByOneMinute={incrementSessionLengthByOneMinute}
28 decrementSessionLengthByOneMinute={decrementSessionLengthByOneMinute}
29 />
30 </div>
31 );
32}
33
34export default App;

In Session.jsx, we must accept these props by declaring them as parameters to our functional component:

1// Session.jsx
2import moment from 'moment';
3import React from 'react';
4
5const Session = ({
6 sessionLengthInSeconds, // this is where we accept the props
7 incrementSessionLengthByOneMinute,
8 decrementSessionLengthByOneMinute,
9}) => {
10 const sessionLengthInMinutes = moment.duration(sessionLengthInSeconds, 's').minutes();
11 return (
12 <div>
13 <p id="session-label">Session</p>
14 <p id="session-length">{sessionLengthInMinutes}</p>
15 <button id="session-increment" onClick={incrementSessionLengthByOneMinute}>
16 +
17 </button>
18 <button id="session-decrement" onClick={decrementSessionLengthByOneMinute}>
19 -
20 </button>
21 </div>
22 );
23};
24
25export default Session;

If everything was done correctly, the app should work just as it did before. Now, take a few minutes and lift the Break component’s state up by yourself.

All done? App.js and Break.jsx should look as follows:

1// App.js
2import React, { useState } from 'react';
3import './App.css';
4import Break from './components/Break';
5import Session from './components/Session';
6
7function App() {
8 const [breakLengthInSeconds, setBreakLengthInSeconds] = useState(300);
9 const [sessionLengthInSeconds, setSessionLengthInSeconds] = useState(60 * 25);
10
11 const decrementBreakLengthByOneMinute = () => {
12 const newBreakLengthInSeconds = breakLengthInSeconds - 60;
13 if (newBreakLengthInSeconds < 0) {
14 setBreakLengthInSeconds(0);
15 } else {
16 setBreakLengthInSeconds(newBreakLengthInSeconds);
17 }
18 };
19 const incrementBreakLengthByOneMinute = () => setBreakLengthInSeconds(breakLengthInSeconds + 60);
20
21 const decrementSessionLengthByOneMinute = () => {
22 const newSessionLengthInSeconds = sessionLengthInSeconds - 60;
23 if (newSessionLengthInSeconds < 0) {
24 setSessionLengthInSeconds(0);
25 } else {
26 setSessionLengthInSeconds(newSessionLengthInSeconds);
27 }
28 };
29 const incrementSessionLengthByOneMinute = () =>
30 setSessionLengthInSeconds(sessionLengthInSeconds + 60);
31
32 return (
33 <div className="App">
34 <Break
35 breakLengthInSeconds={breakLengthInSeconds}
36 incrementBreakLengthByOneMinute={incrementBreakLengthByOneMinute}
37 decrementBreakLengthByOneMinute={decrementBreakLengthByOneMinute}
38 />
39 <Session
40 sessionLengthInSeconds={sessionLengthInSeconds}
41 incrementSessionLengthByOneMinute={incrementSessionLengthByOneMinute}
42 decrementSessionLengthByOneMinute={decrementSessionLengthByOneMinute}
43 />
44 </div>
45 );
46}
47
48export default App;
1// Break.jsx
2import moment from 'moment';
3import React from 'react';
4
5const Break = ({
6 breakLengthInSeconds,
7 incrementBreakLengthByOneMinute,
8 decrementBreakLengthByOneMinute,
9}) => {
10 const breakLengthInMinutes = moment.duration(breakLengthInSeconds, 's').minutes();
11 return (
12 <div>
13 <p id="break-label">Break</p>
14 <p id="break-length">{breakLengthInMinutes}</p>
15 <button id="break-increment" onClick={incrementBreakLengthByOneMinute}>
16 +
17 </button>
18 <button id="break-decrement" onClick={decrementBreakLengthByOneMinute}>
19 -
20 </button>
21 </div>
22 );
23};
24
25export default Break;

TimeLeft Component

Great, we’re ready to create our TimeLeft component and initialize its value.

In your components directory, create and export an empty component named TimeLeft. Then, import this component in App.js and render it between <Break /> and <Session />.

Now, that you’ve done that, pass sessionLengthInSeconds (we’ll use it to initialize the timeLeft in our TimeLeft component) from the App component to the TimeLeft component.

Lastly, accept these props in TimeLeft. Use the sessionLengthInSeconds prop to initialize a new state (remember useState?) variable called timeLeft. Render out timeLeft in a <p> tag with the id “time-left”.

You should be able to all this by yourself with everything you’ve learned up to this point in this tutorial series. I strongly suggest you stop here and try all this yourself before going on and seeing the answer below.

Here’s what that looks like:

1// components/TimeLeft.jsx
2import React from 'react';
3import { useState } from 'react';
4
5const TimeLeft = ({ sessionLengthInSeconds }) => {
6 const [timeLeft] = useState(sessionLengthInSeconds);
7
8 return <p id="time-left">{timeLeft}</p>;
9};
10
11export default TimeLeft;
1// App.js
2import React, { useState } from 'react';
3import './App.css';
4import Break from './components/Break';
5import Session from './components/Session';
6import TimeLeft from './components/TimeLeft';
7
8function App() {
9 const [breakLengthInSeconds, setBreakLengthInSeconds] = useState(300);
10 const [sessionLengthInSeconds, setSessionLengthInSeconds] = useState(60 * 25);
11
12 const decrementBreakLengthByOneMinute = () => {
13 const newBreakLengthInSeconds = breakLengthInSeconds - 60;
14 if (newBreakLengthInSeconds < 0) {
15 setBreakLengthInSeconds(0);
16 } else {
17 setBreakLengthInSeconds(newBreakLengthInSeconds);
18 }
19 };
20 const incrementBreakLengthByOneMinute = () => setBreakLengthInSeconds(breakLengthInSeconds + 60);
21
22 const decrementSessionLengthByOneMinute = () => {
23 const newSessionLengthInSeconds = sessionLengthInSeconds - 60;
24 if (newSessionLengthInSeconds < 0) {
25 setSessionLengthInSeconds(0);
26 } else {
27 setSessionLengthInSeconds(newSessionLengthInSeconds);
28 }
29 };
30 const incrementSessionLengthByOneMinute = () =>
31 setSessionLengthInSeconds(sessionLengthInSeconds + 60);
32
33 return (
34 <div className="App">
35 <Break
36 breakLengthInSeconds={breakLengthInSeconds}
37 incrementBreakLengthByOneMinute={incrementBreakLengthByOneMinute}
38 decrementBreakLengthByOneMinute={decrementBreakLengthByOneMinute}
39 />
40 <TimeLeft sessionLengthInSeconds={sessionLengthInSeconds} />
41 <Session
42 sessionLengthInSeconds={sessionLengthInSeconds}
43 incrementSessionLengthByOneMinute={incrementSessionLengthByOneMinute}
44 decrementSessionLengthByOneMinute={decrementSessionLengthByOneMinute}
45 />
46 </div>
47 );
48}
49
50export default App;

Well done! If you did everything correct, the TimeLeft component should render out the time left…but in seconds. We should format this in MM:SS format, as per the freeCodeCamp spec. But how? 🤔

Formatting Moment Durations to MM:SS Format

To format Moment durations, we’ll use the moment-duration-format plugin. First, let’s install the package:

1npm install moment-duration-format

To “plug in” the plugin, do the following in TimeLeft.jsx:

1// TimeLeft.jsx
2import moment from 'moment';
3import momentDurationFormatSetup from 'moment-duration-format';
4import React from 'react';
5import { useState } from 'react';
6
7momentDurationFormatSetup(moment);
8// ... the rest of your component here

With that done, we’re ready to format the component. As per the moment-duration-format documentation, we’ll simply create a duration from timeLeft, add call the format() function with a format string argument and render out the return value:

1// TimeLeft.jsx
2import moment from 'moment';
3import momentDurationFormatSetup from 'moment-duration-format';
4import React from 'react';
5import { useState } from 'react';
6
7momentDurationFormatSetup(moment);
8
9const TimeLeft = ({ sessionLengthInSeconds }) => {
10 const [timeLeft] = useState(sessionLengthInSeconds);
11
12 const formattedTimeLeft = moment.duration(timeLeft, 's').format('mm:ss');
13 return <p id="time-left">{formattedTimeLeft}</p>;
14};
15
16export default TimeLeft;

Note that moment.duration(timeLeft, ’s’) is almost identical to the code we have in Break.jsx and Session.jsx. It simply creates a Moment duration. The only new part of this is the format function and the format template string argument.

👏 You Made It! 👏

You’ve taken steps towards completing the freeCodeCamp Pomodoro Clock project and now know how to pass props to components and lift state up.

If you enjoyed this tutorial, follow me on:

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 01: React Functional Components and Local State Using Hooks

freeCodeCamp Pomodoro Clock 01: React Functional Components and Local State Welcome back! This tutorial is the second installment of a…

February 2nd, 2020 · 4 min read

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
© 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/