Introduction
Hi everyone, I’m Keni (@keni_solopreneur).
This is my first post on DEV, and I’d like to share the journey of creating my side project: Pomodoro Flow 🍅 — a minimalist Pomodoro timer that integrates YouTube directly.
I’m not a professional software engineer by trade. I actually started this project simply because I wanted a focus timer that could play my favorite YouTube music without having to constantly switch tabs. What started as a small frustration turned into a real project where I learned React, TypeScript, Zustand, and a lot about building in public.
In this post, I’ll go through:
- Why I decided to build this tool in the first place
- The stack I chose (React, Vite, TypeScript, Zustand) and why
- How I integrated YouTube with
react-youtube
and the IFrame API - How Zustand made state management so much simpler
- The challenges I faced and lessons I learned along the way
- What I’m planning next for this project
The Problem: Timer + YouTube = Too Many Tabs
I’ve always liked the Pomodoro Technique: 25 minutes of focused work, followed by a short break. It helps me stay on track, especially when working on personal projects after my day job.
But there was always a catch. Most Pomodoro apps provide built-in music or ambient sounds. And while that works for some people, it never worked for me. Some days I need lo-fi hip hop, other days it’s rain sounds, and sometimes it’s even game commentary running in the background. The music of the day matters a lot for my focus.
At first, my workflow looked like this:
- One tab open for the timer
- Another tab open for YouTube
- Constant switching back and forth
It was distracting and killed my flow.
So I thought: what if I just build the tool I need myself?
Learning React in 3 Weeks (as a Non-Engineer)
Here’s the honest part: I had zero web development experience before this.
I’m a full-time automation engineer, but my work is more about Python and PLCs than frontend. So, starting with React and TypeScript felt intimidating.
To get started, I did a few things:
- Watched YouTube tutorials on React basics
- Took a Udemy course to understand component-based development
- Used ChatGPT and Cline (AI coding assistant) to accelerate learning
- Built small test components before putting them into my app
Three weeks later, I had my MVP: a Pomodoro timer with a working YouTube embed.
After another three weeks of refining the UI, fixing bugs, and polishing the UX, I had something that I felt confident enough to share: Pomodoro Flow.
Tech Stack
- React (with Vite) → Fast, modern, and great DX
- TypeScript → Type safety made me more confident, especially as a beginner
- Zustand → Lightweight and intuitive state management
- react-youtube → A simple wrapper around the YouTube IFrame Player API
I chose these tools because I wanted something minimal but reliable. Redux felt like overkill for this project, and Context API alone wasn’t enough for the level of state management I needed.
Key Feature: Syncing Timer and YouTube Playback
The core of Pomodoro Flow is simple:
- When the timer starts → play the YouTube video
- When the timer pauses → pause the video
Here’s the simplified implementation:
import YouTube from 'react-youtube';
import { useTimerStore } from './stores/timerStore';
import { useYouTubeStore } from './stores/youtubeStore';
import { useEffect } from 'react';
const YouTubePlayer = () => {
const { isRunning } = useTimerStore();
const { player, setPlayer } = useYouTubeStore();
useEffect(() => {
if (!player) return;
isRunning ? player.playVideo() : player.pauseVideo();
}, [isRunning, player]);
const onReady = (event: any) => {
setPlayer(event.target);
};
return <YouTube videoId="YOUR_VIDEO_ID" onReady={onReady} />;
};
Why Zustand?
I briefly considered Redux, Context API, and even Recoil. In the end, Zustand won because:
- It’s lightweight (no boilerplate, just functions and hooks)
- State slices make it easy to subscribe only to what you need
- It plays nicely with TypeScript
Here’s a simple example of the timer store:
// src/stores/timerStore.ts
import { create } from 'zustand';
type TimerState = {
isRunning: boolean;
start: () => void;
stop: () => void;
};
export const useTimerStore = create<TimerState>((set) => ({
isRunning: false,
start: () => set({ isRunning: true }),
stop: () => set({ isRunning: false }),
}));
Challenges and Solutions
YouTube Player Load Timing
One issue I faced was that sometimes the YouTube player wasn’t fully ready when the timer state changed.
✅ Solution: Only control playback once onReady
sets the player instance in Zustand.
Avoiding Unnecessary Re-renders
Another problem was re-rendering too many components when state changed.
✅ Solution: With Zustand, I could subscribe only to the specific state slice (isRunning
) that I needed, preventing unrelated UI updates.
TypeScript with Third-Party Libraries
The react-youtube
package didn’t provide perfect type support.
✅ Solution: I wrote wrapper types and interfaces to ensure type safety across my codebase.
The Result: Pomodoro Flow
After about 6 weeks of learning and building, the result is Pomodoro Flow — a minimal Pomodoro timer that integrates directly with YouTube.
👉 Try it here: pomodoro-flow.com
Key features include:
- Paste any YouTube link you want
- Play your favorite music, ASMR, or ambient sounds while working
- No more switching tabs — just one clean space to focus
What’s Next?
This is still just an MVP, but I already have a roadmap in mind:
- Spotify and other music service integrations
- Simple task management features
- Analytics to visualize productivity sessions
I’ll continue building in public and sharing progress here on Dev.to. 🚀
Final Thoughts
This project started from a small personal frustration, but it became a great learning journey with React, TypeScript, Zustand, and the YouTube API.
If you’re considering starting your own side project, my advice is simple: just start.
You’ll learn far more than you expect.
👉 Try Pomodoro Flow: https://pomodoro-flow.com
👉 Follow me here on Dev.to — I’ll be sharing more updates and lessons learned.
Thanks for reading, and happy focusing! 🍅
Top comments (0)