DEV Community

Nat Ravenhill
Nat Ravenhill

Posted on

4

Refactoring the Pomodoro Timer: Components

Previously I wrote a Pomodoro timer app. The code is contained in a single React component. This is generally bad practice - we want components to each be responsible for a single bit of functionality and be reusable, so we will we split out functionality into smaller components.

First I made a folder called "components", which will house the sub components and their styles.

There are 3 elements we can immediately split out:

  1. The main timer and button ("Tomato" element)
  2. The Break length controls
  3. The Session length controls

image

Break Length

This section has a breakLength value contained in the state and two button handler methods to increase/decrease this value.

First, we create a new component called BreakLength and paste the JSX into the render method. As we are now in a child component, and React has unidirectional data flow, we cannot update the breakLength value in the parent component from the BreakLength component.

Therefore, we pass a reference to the method into the BreakLength component as a property and call this in a new onClick handler. This will call the parent method and update its state.

The breakLength value is passed to the new component as a child property. When the state is changed in the parent, this will change the property value and re-render the BreakLength component.

class BreakLength extends Component {
handleBreakIncrement = () => {
this.props.handleBreakIncrement();
};
handleBreakDecrement = () => {
this.props.handleBreakDecrement();
};
render() {
return (
<div className="Break-length">
<h2 id="break-label">Break Length</h2>
<h3 id="break-length">{this.props.breakLength}</h3>
<button
id="break-decrement"
onClick={this.handleBreakDecrement.bind(this)}
>
<FontAwesomeIcon icon={faMinusCircle} size="2x" />
</button>
<button
id="break-increment"
onClick={this.handleBreakIncrement.bind(this)}
>
<FontAwesomeIcon icon={faPlusCircle} size="2x" />
</button>
</div>
);
}
}
view raw BreakLength.jsx hosted with ❤ by GitHub

Session Length

This is refactored in the same way:

class SessionLength extends Component {
handleSessionIncrement = () => {
this.props.handleSessionIncrement();
};
handleSessionDecrement = () => {
this.props.handleSessionDecrement();
};
render() {
return (
<div className="Session-length">
<h2 id="session-label">Session Length</h2>
<h3 id="session-length">{this.props.sessionLength}</h3>
<button
id="session-decrement"
onClick={this.handleSessionDecrement.bind(this)}
>
<FontAwesomeIcon icon={faMinusCircle} size="2x" />
</button>
<button
id="session-increment"
onClick={this.handleSessionIncrement.bind(this)}
>
<FontAwesomeIcon icon={faPlusCircle} size="2x" />
</button>
</div>
);
}
}

Tomato

The Tomato has 2 functions - handleStartStop and handleReset. Again, we pass references to these as properties and call them in the onClick handlers in the child Tomato component. The minutes and second properties are passed as props to the Tomato component.

class Tomato extends Component {
handleStartStop = () => {
this.props.handleStartStop();
};
handleReset = () => {
this.props.handleReset();
};
render() {
return (
<div className="tomato">
<p id="time-left">
{this.props.minutes.toString().padStart(2, "0")}:
{this.props.seconds.toString().padStart(2, "0")}
</p>
<button id="start_stop" onClick={this.handleStartStop.bind(this)}>
<FontAwesomeIcon icon={faPlay} color="#FDE9B0" size="2x" />
<FontAwesomeIcon icon={faPause} color="#FDE9B0" size="2x" />
</button>
<button id="reset" onClick={this.handleReset.bind(this)}>
<FontAwesomeIcon icon={faRedo} color="#FDE9B0" size="2x" />
</button>
</div>
);
}
}
view raw Pomodoro Tomato hosted with ❤ by GitHub

Now our main component is much smaller and we have a better separation of concerns. We can also extract some of the styles in Session.css that only apply to the Tomato into a new CSS file and include it just in this component.

Sentry blog image

How I fixed 20 seconds of lag for every user in just 20 minutes.

Our AI agent was running 10-20 seconds slower than it should, impacting both our own developers and our early adopters. See how I used Sentry Profiling to fix it in record time.

Read more

Top comments (1)

Collapse
 
thomasstep profile image
Thomas Step

I wonder if you could reduce the BreakLength and SessionLength components down into a single component as well. They seem to perform similar actions. Maybe the text in the h2 tag and the handle*Decrement/handle*Increment functions can all be passed in as props.

Do you have this hosted anywhere?

Postmark Image

Speedy emails, satisfied customers

Are delayed transactional emails costing you user satisfaction? Postmark delivers your emails almost instantly, keeping your customers happy and connected.

Sign up

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay