Learning Vue Part 3: Building a pomodoro timer
William Kwadwo Owusu
Posted on September 8, 2024
For my third project, I decided to create a Pomodoro Timer, a productivity tool designed to help manage deep work sessions followed by short breaks. In building this, I expanded my understanding of Vue.js, particularly when it comes to managing time-based states, form handling, and user interactions. Here’s an overview of the key lessons I’ve learned.
1. Handling Form Submissions with Vue: Custom Work and Break Times
The first part of the project involved setting up a simple form to allow the user to set their desired work and break times. Just like in my previous projects, I used Vue’s @submit.prevent
to stop the form from refreshing the page:
<form class="mt-4" @submit.prevent="setTime">
<div class="form-row mx-2">
<div class="col-4">
<label class="font-weight-bold">Work (min)</label>
<input type="number" v-model="workTime" class="form-control" placeholder="eg: 25">
</div>
<div class="col-4">
<label class="font-weight-bold">Break (min)</label>
<input type="number" v-model="breakTime" class="form-control" placeholder="eg: 5">
</div>
<div class="col-4">
<label style="visibility: hidden;">Submit</label>
<input type="submit" class=" form-control btn btn-outline-light text-white">
</div>
</div>
</form>
Vue’s v-model
allows for two-way data binding, making it easy to capture and manipulate the input values for work and break time. Validating user input (e.g., ensuring the input is a valid number) was also something I had to account for, which I handled within the setTime
method:
const setTime = () => {
if (workTime.value == '' || breakTime.value == '' || workTime.value <= 0 || breakTime.value <= 0) {
alert("Please enter a valid number of minutes!");
return;
}
totalSeconds.value = (workTime.value * 60);
initialWorkTime = (workTime.value * 60);
initialBreakTime = (breakTime.value * 60);
updateMinutesSeconds();
alert("Work and break time successfully updated");
};
This was a valuable exercise in handling form validation and ensuring data integrity.
2. State Management: Tracking Time with Reactive Properties
Managing the timer functionality required me to keep track of the number of seconds and minutes remaining. Vue’s ref
method was crucial for this, as it made the data reactive and ensured the UI updated automatically as the timer counted down:
const totalSeconds = ref(initialWorkTime);
const seconds = ref(totalSeconds.value % 60);
const minutes = ref(Math.floor(totalSeconds.value / 60));
To continuously update the timer, I used setInterval
to decrement the time every second:
const startTimer = () => {
isPaused.value = false;
if (!nIntervalId.value) {
nIntervalId.value = setInterval(() => {
if (totalSeconds.value > 0 && isPaused.value == false) {
totalSeconds.value--;
updateMinutesSeconds();
} else if (totalSeconds.value == 0) {
clearInterval(nIntervalId.value);
nIntervalId.value = null;
setBreak();
}
}, 1000);
}
};
This not only allowed the timer to work correctly but also highlighted how Vue’s reactivity system handles time-sensitive operations well.
3. Computed Properties: Formatting Time for Display
When building a timer, it's important to display the time in a readable format. To ensure that the seconds display with a leading zero (e.g., 04
instead of 4
), I used a computed property in Vue:
const formattedSeconds = computed(() => {
return seconds.value < 10 ? `0${seconds.value}` : seconds.value;
});
This was a great opportunity to use Vue’s computed
property, which efficiently recalculates values only when dependencies change, making it ideal for time formatting.
4. Handling Timer Control: Start, Pause, Reset
Implementing start, pause, and reset functionalities required careful management of the timer’s state. I used Vue’s reactivity to track whether the timer was running or paused:
let isPaused = ref(false);
-
Start Timer: This resumes the countdown by setting
isPaused
tofalse
and starting the interval if it isn’t already running. -
Pause Timer: I simply set
isPaused
totrue
when the user wants to pause the timer. - Reset Timer: This clears the interval and restores the timer to its initial state:
const resetTimer = () => {
clearInterval(nIntervalId.value);
nIntervalId.value = null;
totalSeconds.value = initialWorkTime;
updateMinutesSeconds();
isPaused.value = false;
workPeriod = true;
};
This was a key lesson in how to manage state transitions in a Vue app, especially when working with intervals and reactive variables.
5. Switching Between Work and Break Periods
A Pomodoro timer involves alternating between a deep work period and a break period. When the work period ends, I switch to break mode using the setBreak
method:
const setBreak = () => {
totalSeconds.value = initialBreakTime;
updateMinutesSeconds();
workPeriod = false;
};
This logic ensures that after the timer hits zero during the work session, it transitions automatically to the break session, and vice versa.
6. Enhancing User Experience with Tooltips and Icons
To make the UI more user-friendly, I added Bootstrap icons and tooltips. For example, I used buttons with icons for Start, Pause, Reset, and Switch to Break, each with tooltips to clarify their functionality:
<button class="btn btn-lg btn-light text-danger mx-3" data-toggle="tooltip" data-placement="top" title="Start/Resume timer" @click="startTimer"><i class="bi bi-play-fill"></i></button>
<button class="btn btn-lg btn-light text-danger mx-3" data-toggle="tooltip" data-placement="top" title="Pause timer" @click="pauseTimer"><i class="bi bi-pause-fill"></i></button>
<button class="btn btn-lg btn-light text-danger mx-3" data-toggle="tooltip" data-placement="top" title="Reset timer" @click="resetTimer"><i class="bi bi-arrow-counterclockwise"></i></button>
<button class="btn btn-lg btn-light text-danger mx-3" data-toggle="tooltip" data-placement="top" title="Go to break time" @click="setBreak"><i class="bi bi-lightning-charge-fill"></i></button>
These small enhancements added a lot to the user experience by making the interface more intuitive.
Final Thoughts: What I’ve Gained from Building a Pomodoro Timer
This project deepened my understanding of how Vue.js can be used for time-based, interactive apps. I learned how to:
- Effectively manage timers and state transitions with
setInterval
. - Use computed properties to handle dynamic UI changes, such as formatting time.
- Implement a user-friendly form to capture and validate input, ensuring smooth user interaction.
- Create an intuitive user interface with Bootstrap icons and tooltips.
By completing this Pomodoro Timer, I gained a solid grasp of more advanced state management and how to create user-friendly experiences with Vue.
Looking forward to more Vue adventures!
Posted on September 8, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.