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.
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):
// App.js
import React, { useState } from 'react';
import './App.css';
import Break from './components/Break';
import Session from './components/Session';
function App() {
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);
return (
<div className="App">
<Break />
<Session />
</div>
);
}
export default App;
// Session.jsx
import moment from 'moment';
import React from 'react';
const Session = () => {
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;
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:
// App.js
import React, { useState } from 'react';
import './App.css';
import Break from './components/Break';
import Session from './components/Session';
function App() {
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);
return (
<div className="App">
<Break />
{/* pass props below! */}
<Session
sessionLengthInSeconds={sessionLengthInSeconds}
incrementSessionLengthByOneMinute={incrementSessionLengthByOneMinute}
decrementSessionLengthByOneMinute={decrementSessionLengthByOneMinute}
/>
</div>
);
}
export default App;
In Session.jsx
, we must accept these props by declaring them as parameters to our functional component:
// Session.jsx
import moment from 'moment';
import React from 'react';
const Session = ({
sessionLengthInSeconds, // this is where we accept the props
incrementSessionLengthByOneMinute,
decrementSessionLengthByOneMinute,
}) => {
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;
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:
// App.js
import React, { useState } from 'react';
import './App.css';
import Break from './components/Break';
import Session from './components/Session';
function App() {
const [breakLengthInSeconds, setBreakLengthInSeconds] = useState(300);
const [sessionLengthInSeconds, setSessionLengthInSeconds] = useState(60 * 25);
const decrementBreakLengthByOneMinute = () => {
const newBreakLengthInSeconds = breakLengthInSeconds - 60;
if (newBreakLengthInSeconds < 0) {
setBreakLengthInSeconds(0);
} else {
setBreakLengthInSeconds(newBreakLengthInSeconds);
}
};
const incrementBreakLengthByOneMinute = () => setBreakLengthInSeconds(breakLengthInSeconds + 60);
const decrementSessionLengthByOneMinute = () => {
const newSessionLengthInSeconds = sessionLengthInSeconds - 60;
if (newSessionLengthInSeconds < 0) {
setSessionLengthInSeconds(0);
} else {
setSessionLengthInSeconds(newSessionLengthInSeconds);
}
};
const incrementSessionLengthByOneMinute = () =>
setSessionLengthInSeconds(sessionLengthInSeconds + 60);
return (
<div className="App">
<Break
breakLengthInSeconds={breakLengthInSeconds}
incrementBreakLengthByOneMinute={incrementBreakLengthByOneMinute}
decrementBreakLengthByOneMinute={decrementBreakLengthByOneMinute}
/>
<Session
sessionLengthInSeconds={sessionLengthInSeconds}
incrementSessionLengthByOneMinute={incrementSessionLengthByOneMinute}
decrementSessionLengthByOneMinute={decrementSessionLengthByOneMinute}
/>
</div>
);
}
export default App;
// Break.jsx
import moment from 'moment';
import React from 'react';
const Break = ({
breakLengthInSeconds,
incrementBreakLengthByOneMinute,
decrementBreakLengthByOneMinute,
}) => {
const breakLengthInMinutes = moment.duration(breakLengthInSeconds, 's').minutes();
return (
<div>
<p id="break-label">Break</p>
<p id="break-length">{breakLengthInMinutes}</p>
<button id="break-increment" onClick={incrementBreakLengthByOneMinute}>
+
</button>
<button id="break-decrement" onClick={decrementBreakLengthByOneMinute}>
-
</button>
</div>
);
};
export 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:
// components/TimeLeft.jsx
import React from 'react';
import { useState } from 'react';
const TimeLeft = ({ sessionLengthInSeconds }) => {
const [timeLeft] = useState(sessionLengthInSeconds)
return <p id="time-left">{timeLeft}</p>;
};
export default TimeLeft;
// App.js
import React, { useState } from 'react';
import './App.css';
import Break from './components/Break';
import Session from './components/Session';
import TimeLeft from './components/TimeLeft';
function App() {
const [breakLengthInSeconds, setBreakLengthInSeconds] = useState(300);
const [sessionLengthInSeconds, setSessionLengthInSeconds] = useState(60 * 25);
const decrementBreakLengthByOneMinute = () => {
const newBreakLengthInSeconds = breakLengthInSeconds - 60;
if (newBreakLengthInSeconds < 0) {
setBreakLengthInSeconds(0);
} else {
setBreakLengthInSeconds(newBreakLengthInSeconds);
}
};
const incrementBreakLengthByOneMinute = () => setBreakLengthInSeconds(breakLengthInSeconds + 60);
const decrementSessionLengthByOneMinute = () => {
const newSessionLengthInSeconds = sessionLengthInSeconds - 60;
if (newSessionLengthInSeconds < 0) {
setSessionLengthInSeconds(0);
} else {
setSessionLengthInSeconds(newSessionLengthInSeconds);
}
};
const incrementSessionLengthByOneMinute = () =>
setSessionLengthInSeconds(sessionLengthInSeconds + 60);
return (
<div className="App">
<Break
breakLengthInSeconds={breakLengthInSeconds}
incrementBreakLengthByOneMinute={incrementBreakLengthByOneMinute}
decrementBreakLengthByOneMinute={decrementBreakLengthByOneMinute}
/>
<TimeLeft sessionLengthInSeconds={sessionLengthInSeconds} />
<Session
sessionLengthInSeconds={sessionLengthInSeconds}
incrementSessionLengthByOneMinute={incrementSessionLengthByOneMinute}
decrementSessionLengthByOneMinute={decrementSessionLengthByOneMinute}
/>
</div>
);
}
export 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:
npm install moment-duration-format
To “plug in” the plugin, do the following in TimeLeft.jsx
:
// TimeLeft.jsx
import moment from 'moment';
import momentDurationFormatSetup from 'moment-duration-format';
import React from 'react';
import { useState } from 'react';
momentDurationFormatSetup(moment);
// ... 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:
// TimeLeft.jsx
import moment from 'moment';
import momentDurationFormatSetup from 'moment-duration-format';
import React from 'react';
import { useState } from 'react';
momentDurationFormatSetup(moment);
const TimeLeft = ({ sessionLengthInSeconds }) => {
const [timeLeft] = useState(sessionLengthInSeconds);
const formattedTimeLeft = moment.duration(timeLeft, 's').format('mm:ss');
return <p id="time-left">{formattedTimeLeft}</p>;
};
export 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.
Top comments (0)