DEV Community

Cover image for Building a React Habit Tracker with Claude Code - Part 1: From Idea to MVP
Anirban Majumdar
Anirban Majumdar

Posted on

Building a React Habit Tracker with Claude Code - Part 1: From Idea to MVP

Building a Full-Stack Habit Tracker with Claude Code - Part 1: From Idea to MVP

Introduction: Why Another Habit Tracker?

I started this a demo project to learn how can claude code help as an AI dev partner , I used this demo project to understand and learn different ways to effectively use Claude code and its feature

Here are some important topics that I learned while making this project

  • Claude.md file

  • context window

  • Tools and permission

  • Planning and Thinking

  • Slash Command

  • Custom Slash command

  • MCP Server

  • Sub Agents

Follow along to see how I used this learning while creating this Habit Tracker app in React.

Here is what the app does:

  • ๐Ÿš€ Fast - No login, no setup, just start tracking
  • ๐ŸŽจ Beautiful - A UI I'd actually want to use daily
  • ๐Ÿ“Š Insightful - Show me trends, not just checkboxes
  • ๐Ÿ”’ Private - My data stays in my browser

I built this entire app with Claude Code as my AI pair programmer. And I'm going to show you exactly how.

But first lets start with basics

What is Claude Code?

Before we dive in, let me explain what made this project possible. Claude Code is Anthropic's official CLI tool that brings Claude AI directly into your development workflow. Think of it as an AI pair programmer that can:

  • Read and understand your entire codebase
  • Write, edit, and refactor code
  • Run terminal commands
  • Debug issues
  • Suggest architecture improvements

It's not just a chatbot - it's a full development partner.

The Game Plan

Here's what I wanted to build:

Core Features (MVP):

  • โœ… Add, edit, and delete habits
  • โœ… Track habits by date
  • โœ… Four status types: Completed, To Do, Not Completed, Skipped
  • โœ… Data persistence with localStorage
  • โœ… Dark/light theme toggle
  • โœ… Basic analytics and trends

Tech Stack:

  • React (latest version)
  • React Router for navigation
  • Pure CSS (no frameworks - keeping it simple)
  • localStorage for data persistence (no backend needed!)

Let's build it! ๐Ÿ› ๏ธ

Phase 1: Project Setup (The Boring-But-Essential Part)

I started by asking Claude Code to initialize a React project:

npx create-react-app habit-tracker
cd habit-tracker
npm install react-router-dom
Enter fullscreen mode Exit fullscreen mode

Claude suggested this project structure, which turned out to be perfect:

src/
โ”œโ”€โ”€ components/       # Reusable UI components
โ”œโ”€โ”€ pages/           # Route components (Home, Trends, About)
โ”œโ”€โ”€ hooks/           # Custom React hooks
โ”œโ”€โ”€ styles/          # CSS modules
โ””โ”€โ”€ utils/           # Helper functions
Enter fullscreen mode Exit fullscreen mode

๐Ÿ’ก Learning #1: Having a clear structure from day one saves hours of refactoring later.

Phase 2: Building the Data Model

The first real code we wrote together was the data model. I described what I wanted, and Claude helped me design a simple but extensible structure:

// Each habit entry looks like this:
{
  id: "1712345678901",           // Timestamp-based unique ID
  habit: "Morning workout",       // What you're tracking
  date: "2026-04-08",            // ISO date format
  status: "Completed"            // Completed | To Do | Not Completed | Skipped
}
Enter fullscreen mode Exit fullscreen mode

Clean, simple, and stored as a JSON array in localStorage. No over-engineering!

Phase 3: State Management Strategy

Here's where Claude Code really shined. Instead of reaching for Redux or Zustand, it suggested a simple but powerful approach:

// App.js - The main component
function App() {
  // Load habits from localStorage on mount
  const [habits, setHabits] = useState(() => {
    const saved = localStorage.getItem('habit-tracker-habits');
    return saved ? JSON.parse(saved) : [];
  });

  // Auto-save to localStorage whenever habits change
  useEffect(() => {
    localStorage.setItem('habit-tracker-habits', JSON.stringify(habits));
  }, [habits]);

  // ... rest of the app
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿ’ก Learning #2: You don't always need a state management library. React hooks + localStorage can handle a lot!

Phase 4: The HabitForm Component

This was our first real UI component. I asked Claude to create a form that felt intuitive, and here's what we built together:

// src/components/HabitForm.js
import { useState } from 'react';
import TextInput from './TextInput';
import DateSelector from './DateSelector';
import StatusSelector from './StatusSelector';

function HabitForm({ onAddHabit }) {
  const [habit, setHabit] = useState('');
  const [date, setDate] = useState(new Date().toISOString().split('T')[0]);
  const [status, setStatus] = useState('To Do');

  const handleSubmit = (e) => {
    e.preventDefault();

    if (!habit.trim()) return; // Basic validation

    onAddHabit({
      id: Date.now().toString(),
      habit: habit.trim(),
      date,
      status
    });

    // Clear form
    setHabit('');
    setStatus('To Do');
  };

  return (
    <form className="habit-form" onSubmit={handleSubmit}>
      <h2>โž• Add New Habit</h2>

      <TextInput
        label="Habit"
        value={habit}
        onChange={(e) => setHabit(e.target.value)}
        placeholder="Enter habit (e.g., Morning workout, Read 30 mins)"
      />

      <div className="form-row">
        <DateSelector
          value={date}
          onChange={(e) => setDate(e.target.value)}
        />

        <StatusSelector
          value={status}
          onChange={(e) => setStatus(e.target.value)}
        />
      </div>

      <button type="submit" className="btn-primary">
        โž• Add Habit
      </button>
    </form>
  );
}
Enter fullscreen mode Exit fullscreen mode

Key Design Decisions:

  1. Composition over monolith: Breaking the form into smaller components (TextInput, DateSelector, StatusSelector) made it easier to maintain and reuse.

  2. Emojis as icons: Instead of icon libraries, we used emojis. Lightweight and universally supported!

  3. Immediate feedback: The form clears immediately after submission, giving users a sense of accomplishment.

๐Ÿ’ก Learning #3: Small, focused components are easier for AI (and humans!) to understand and modify.

Phase 5: Displaying Habits - The HabitList Component

Now came the fun part - displaying habits in a beautiful, organized way. I wanted habits grouped by date, with special labels for "Today" and "Yesterday".

Claude Code helped me build this smart grouping logic:

// src/components/HabitList.js
function HabitList({ habits, onEdit, onDelete }) {
  // Group habits by date
  const groupedHabits = habits.reduce((groups, habit) => {
    const date = habit.date;
    if (!groups[date]) {
      groups[date] = [];
    }
    groups[date].push(habit);
    return groups;
  }, {});

  // Sort dates in descending order (newest first)
  const sortedDates = Object.keys(groupedHabits).sort((a, b) =>
    new Date(b) - new Date(a)
  );

  // Format date labels
  const formatDateLabel = (dateString) => {
    const date = new Date(dateString);
    const today = new Date();
    const yesterday = new Date(today);
    yesterday.setDate(yesterday.getDate() - 1);

    if (dateString === today.toISOString().split('T')[0]) {
      return 'Today';
    } else if (dateString === yesterday.toISOString().split('T')[0]) {
      return 'Yesterday';
    } else {
      return date.toLocaleDateString('en-US', {
        weekday: 'long',
        year: 'numeric',
        month: 'long',
        day: 'numeric'
      });
    }
  };

  if (habits.length === 0) {
    return (
      <div className="empty-state">
        <span className="empty-icon">๐Ÿ“</span>
        <h2>No habits tracked yet</h2>
        <p>Add your first habit to get started!</p>
      </div>
    );
  }

  return (
    <div className="habit-list">
      <h2>Your Habits</h2>
      {sortedDates.map(date => (
        <div key={date} className="date-group">
          <div className="date-header">
            <h3>{formatDateLabel(date)}</h3>
            <span className="habit-count">
              {groupedHabits[date].length} {groupedHabits[date].length === 1 ? 'habit' : 'habits'}
            </span>
          </div>

          <div className="habits-grid">
            {groupedHabits[date].map(habit => (
              <HabitCard
                key={habit.id}
                habit={habit}
                onEdit={onEdit}
                onDelete={onDelete}
              />
            ))}
          </div>
        </div>
      ))}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Why this works:

  • Smart date grouping: Uses reduce() to organize habits efficiently
  • User-friendly labels: "Today" and "Yesterday" are more intuitive than dates
  • Empty state: Always handle the zero-state gracefully
  • Responsive grid: CSS Grid makes the layout adapt to any screen size

Phase 6: The Theme System (Making It Pretty)

I'm a sucker for dark mode. I asked Claude to implement a theme system that would:

  • Toggle between light and dark
  • Remember user preference
  • Apply smoothly without flashing

Here's the elegant solution we built:

// src/hooks/useTheme.js
import { useState, useEffect } from 'react';

function useTheme() {
  const [theme, setTheme] = useState(() => {
    const saved = localStorage.getItem('habit-tracker-theme');
    return saved || 'light';
  });

  useEffect(() => {
    // Apply theme to root element
    document.documentElement.setAttribute('data-theme', theme);
    // Save to localStorage
    localStorage.setItem('habit-tracker-theme', theme);
  }, [theme]);

  const toggleTheme = () => {
    setTheme(prev => prev === 'light' ? 'dark' : 'light');
  };

  return { theme, toggleTheme };
}

export default useTheme;
Enter fullscreen mode Exit fullscreen mode

And the CSS magic:

/* src/index.css */
:root[data-theme="light"] {
  --bg-primary: #ffffff;
  --bg-secondary: #f5f5f5;
  --text-primary: #333333;
  --text-secondary: #666666;
  --border-color: #e0e0e0;
  --shadow: rgba(0, 0, 0, 0.1);
  --button-bg: #4a90e2;
  --button-hover: #357abd;
}

:root[data-theme="dark"] {
  --bg-primary: #1a1a1a;
  --bg-secondary: #2d2d2d;
  --text-primary: #e0e0e0;
  --text-secondary: #a0a0a0;
  --border-color: #404040;
  --shadow: rgba(0, 0, 0, 0.5);
  --button-bg: #4a90e2;
  --button-hover: #357abd;
}

/* Now all components just use CSS variables */
.habit-card {
  background: var(--bg-primary);
  color: var(--text-primary);
  border: 1px solid var(--border-color);
  box-shadow: 0 2px 4px var(--shadow);
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿ’ก Learning #4: CSS custom properties + the data-theme attribute = the cleanest theme system.

Phase 7: Analytics & Trends

This is where the app went from "useful" to "actually cool". I wanted to show users:

  • Current streaks
  • Completion rates
  • Trends over time

Claude helped me build a utility file for all the calculations:

// src/utils/habitAnalytics.js

// Calculate current streak for a habit
export const calculateCurrentStreak = (habitEntries) => {
  if (!habitEntries.length) return 0;

  // Sort by date descending
  const sorted = [...habitEntries].sort((a, b) =>
    new Date(b.date) - new Date(a.date)
  );

  let streak = 0;
  const today = new Date().toISOString().split('T')[0];
  let checkDate = new Date(today);

  for (const entry of sorted) {
    const entryDate = entry.date;
    const expectedDate = checkDate.toISOString().split('T')[0];

    if (entryDate !== expectedDate) {
      break; // Streak broken
    }

    if (entry.status === 'Completed') {
      streak++;
      checkDate.setDate(checkDate.getDate() - 1);
    } else {
      break; // Not completed = streak broken
    }
  }

  return streak;
};

// Calculate completion rate for a habit
export const calculateCompletionRate = (habitEntries) => {
  if (!habitEntries.length) return 0;

  const completed = habitEntries.filter(h => h.status === 'Completed').length;
  return Math.round((completed / habitEntries.length) * 100);
};

// Group habits by name (case-insensitive)
export const groupHabitsByName = (habits) => {
  return habits.reduce((groups, habit) => {
    const name = habit.habit.toLowerCase();
    if (!groups[name]) {
      groups[name] = [];
    }
    groups[name].push(habit);
    return groups;
  }, {});
};
Enter fullscreen mode Exit fullscreen mode

These pure functions made testing easy and the code maintainable.

Phase 8: Routing with React Router

Finally, I wanted to organize the app into multiple pages:

  • Home: Main tracker interface
  • Trends: Analytics dashboard
  • About: Info about the app
// src/App.js
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import Navigation from './components/Navigation';
import Home from './pages/Home';
import Trends from './pages/Trends';
import About from './pages/About';

function App() {
  // ... state management code ...

  return (
    <Router>
      <div className="App">
        <header className="app-header">
          <h1>Habit Tracker</h1>
          <Navigation />
          <ThemeToggle />
        </header>

        <main>
          <Routes>
            <Route path="/" element={
              <Home
                habits={habits}
                onAddHabit={handleAddHabit}
                onUpdateHabit={handleUpdateHabit}
                onDeleteHabit={handleDeleteHabit}
              />
            } />
            <Route path="/trends" element={<Trends habits={habits} />} />
            <Route path="/about" element={<About />} />
          </Routes>
        </main>
      </div>
    </Router>
  );
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿ’ก Learning #5: Keep routing simple. Pass data as props instead of reaching for context too early.

Working with Claude Code: What I Learned

After spending hours pair programming with Claude Code, here are my biggest takeaways:

1. Be Specific About What You Want

โŒ Bad: "Add a form"
โœ… Good: "Create a form component that accepts habit name, date, and status, with validation and clear button"

2. Let Claude Suggest Architecture

I often asked: "What's the best way to structure this?" Claude's suggestions were consistently better than my first instincts.

3. Iterate in Small Steps

Instead of "build the entire app", I worked feature by feature. This made reviewing code easier and kept momentum high.

4. Ask for Explanations

When Claude wrote code I didn't understand, I asked "Why did you do it this way?" The explanations taught me new patterns.

5. Review Everything

AI is powerful, but not infallible. I read every line of code Claude wrote and tested thoroughly.

The MVP After One Day

After about 6-8 hours of focused development with Claude Code, I had:

โœ… A fully functional habit tracker
โœ… Clean, maintainable code
โœ… Dark/light theme support
โœ… Data persistence with localStorage
โœ… Responsive design
โœ… Basic analytics

Lines of code written: ~1,200
Number of manual file operations: Almost zero
Time saved vs coding alone: Estimated 60-70%

What's Next?

In Part 2, I'll cover:

  • ๐ŸŽจ Adding an 8-category system with emoji icons
  • ๐Ÿ“Š Building comprehensive analytics with streak tracking
  • ๐Ÿ“ˆ Creating beautiful summary cards with progress bars
  • ๐Ÿš€ Deploying to Vercel with proper configuration
  • ๐Ÿงช Testing with Playwright MCP (170+ test cases!)
  • ๐Ÿ“ Generating comprehensive documentation

Try It Yourself

Want to build along? Here's the starter template we used:

npx create-react-app habit-tracker
cd habit-tracker
npm install react-router-dom
npm start
Enter fullscreen mode Exit fullscreen mode

Then open Claude Code and start with: "Help me build a habit tracking app with React. Let's start with the data model."

Conclusion

Building with Claude Code felt like having a senior developer sitting next to me, ready to:

  • Write boilerplate instantly
  • Suggest better patterns
  • Explain complex concepts
  • Catch bugs before I ran the code

Is it perfect? No. I still reviewed every line and made adjustments.

Is it faster? Absolutely. What would have taken me a weekend took an evening.

Did I learn? More than I expected. Claude's explanations taught me new React patterns and best practices.

In Part 2, we'll take this MVP and turn it into a production-ready app with advanced features, comprehensive testing, and real deployment. Stay tuned!


Resources:

Questions? Drop them in the comments below! I'll be responding to all of them.

Found this helpful? Give it a โค๏ธ and follow me for Part 2!


Part 2 coming next week: "Advanced Features, Testing, and Deployment"

AI #React #WebDev #Tutorial #ClaudeCode #ProductivityTools

Top comments (0)