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 inMM: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.

Session
, Break
and TimeLeft
components are all children of App
(thus considered siblings), TimeLeft
cannot currently access sessionLength
or breakLength
to initialize its value:

sessionLength
and breakLength
up to the App
component (hence the name lift state up) and pass it down to Session
, Break
and TimeLeft
:

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.js2import React, { useState } from 'react';3import './App.css';4import Break from './components/Break';5import Session from './components/Session';67function App() {8 const [sessionLengthInSeconds, setSessionLengthInSeconds] = useState(60 * 25);910 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);2021 return (22 <div className="App">23 <Break />24 <Session />25 </div>26 );27}2829export default App;
1// Session.jsx2import moment from 'moment';3import React from 'react';45const 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};2021export 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.js2import React, { useState } from 'react';3import './App.css';4import Break from './components/Break';5import Session from './components/Session';67function App() {8 const [sessionLengthInSeconds, setSessionLengthInSeconds] = useState(60 * 25);910 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);2021 return (22 <div className="App">23 <Break />24 {/* pass props below! */}25 <Session26 sessionLengthInSeconds={sessionLengthInSeconds}27 incrementSessionLengthByOneMinute={incrementSessionLengthByOneMinute}28 decrementSessionLengthByOneMinute={decrementSessionLengthByOneMinute}29 />30 </div>31 );32}3334export default App;
In Session.jsx
, we must accept these props by declaring them as parameters to our functional component:
1// Session.jsx2import moment from 'moment';3import React from 'react';45const Session = ({6 sessionLengthInSeconds, // this is where we accept the props7 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};2425export 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.js2import React, { useState } from 'react';3import './App.css';4import Break from './components/Break';5import Session from './components/Session';67function App() {8 const [breakLengthInSeconds, setBreakLengthInSeconds] = useState(300);9 const [sessionLengthInSeconds, setSessionLengthInSeconds] = useState(60 * 25);1011 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);2021 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);3132 return (33 <div className="App">34 <Break35 breakLengthInSeconds={breakLengthInSeconds}36 incrementBreakLengthByOneMinute={incrementBreakLengthByOneMinute}37 decrementBreakLengthByOneMinute={decrementBreakLengthByOneMinute}38 />39 <Session40 sessionLengthInSeconds={sessionLengthInSeconds}41 incrementSessionLengthByOneMinute={incrementSessionLengthByOneMinute}42 decrementSessionLengthByOneMinute={decrementSessionLengthByOneMinute}43 />44 </div>45 );46}4748export default App;
1// Break.jsx2import moment from 'moment';3import React from 'react';45const 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};2425export 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.jsx2import React from 'react';3import { useState } from 'react';45const TimeLeft = ({ sessionLengthInSeconds }) => {6 const [timeLeft] = useState(sessionLengthInSeconds);78 return <p id="time-left">{timeLeft}</p>;9};1011export default TimeLeft;
1// App.js2import React, { useState } from 'react';3import './App.css';4import Break from './components/Break';5import Session from './components/Session';6import TimeLeft from './components/TimeLeft';78function App() {9 const [breakLengthInSeconds, setBreakLengthInSeconds] = useState(300);10 const [sessionLengthInSeconds, setSessionLengthInSeconds] = useState(60 * 25);1112 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);2122 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);3233 return (34 <div className="App">35 <Break36 breakLengthInSeconds={breakLengthInSeconds}37 incrementBreakLengthByOneMinute={incrementBreakLengthByOneMinute}38 decrementBreakLengthByOneMinute={decrementBreakLengthByOneMinute}39 />40 <TimeLeft sessionLengthInSeconds={sessionLengthInSeconds} />41 <Session42 sessionLengthInSeconds={sessionLengthInSeconds}43 incrementSessionLengthByOneMinute={incrementSessionLengthByOneMinute}44 decrementSessionLengthByOneMinute={decrementSessionLengthByOneMinute}45 />46 </div>47 );48}4950export 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.jsx2import moment from 'moment';3import momentDurationFormatSetup from 'moment-duration-format';4import React from 'react';5import { useState } from 'react';67momentDurationFormatSetup(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.jsx2import moment from 'moment';3import momentDurationFormatSetup from 'moment-duration-format';4import React from 'react';5import { useState } from 'react';67momentDurationFormatSetup(moment);89const TimeLeft = ({ sessionLengthInSeconds }) => {10 const [timeLeft] = useState(sessionLengthInSeconds);1112 const formattedTimeLeft = moment.duration(timeLeft, 's').format('mm:ss');13 return <p id="time-left">{formattedTimeLeft}</p>;14};1516export 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.