Habit Tracking React Native App

A habit tracking application built with React Native and Supabase, designed around a simple observation: most habit trackers treat habits as static objects, while real habits evolve over time.
While using several habit tracking apps, I noticed a recurring problem. Most of the ones I used didn't handle the reality that habits often change as people improve. Editing a habit directly would corrupt historical analytics, making long-term statistics unreliable.
For example, imagine someone starts with:
- Read 10 pages per day
- Daily frequency
After a few months, they decide to switch to:
- Read 50 pages per week
- Weekly frequency
In most habit trackers, editing the habit directly corrupts historical analytics. Old records are suddenly interpreted using the new rules, making long-term statistics unreliable.
This problem became the foundation of Modus Habit.
Habit Versioning
The most challenging part of the project was designing a backend architecture that allows habits to evolve without rewriting history.
Instead of updating a habit in-place, Modus Habit follows an immutable versioning approach. When a habit's structure changes — such as its target, frequency, or tracking mode — the existing habit row is preserved and a new version is inserted. The old version is retired while the new version becomes active.
Both versions are linked through a shared root_id, a self-referencing foreign key that ties every version back to the original habit and forms a lineage chain.
A dedicated versionedOutHabits collection in the application stores retired versions, ensuring historical logs remain attached to the exact habit definition that existed when they were recorded.
Example
Date
Habit Configuration
Jan – Mar
Read 10 pages / day
Apr – Jun
Read 50 pages / week
Both configurations remain independently analyzable, preventing inaccurate success rates and broken streak calculations.
Changing frequency was particularly challenging because frequency determines the structure of the underlying logs themselves. A daily habit may generate hundreds of tracking periods per year, while a weekly habit only generates dozens. Mixing both models inside a single history stream would make analytics inconsistent and difficult to reason about.
To solve this, frequency changes trigger a versioning event while preserving all previous records.
Schema Design and Data Integrity
Two design decisions in the database schema are worth highlighting.
Target Snapshotting
Each habit log stores the target that was active at the time of creation:
target_snapshot: habit.target_count
When calculating statistics, the app resolves the effective target as:
const target = log.target_snapshot ?? habit.target_count;
This means historical progress is always evaluated against the goal that existed at the time, not the current one.
For example, if a user logged 8 pages against a target of 10, and later raised their target to 20, that January log still evaluates as 80% completion — not 40%.
Without this snapshot, retroactive target changes would silently distort historical analytics.
Unified Log Table Across Frequencies
Rather than maintaining separate tables for daily, weekly, and monthly habits, all logs are stored in a single habit_logs table using two fields:
period_start — beginning of the tracking window
period_type — DAILY | WEEKLY | MONTHLY
A daily log for June 5th and a weekly log starting June 1st are simply different rows in the same table.
A uniqueness constraint on:
(habit_id, period_start, period_type)
ensures exactly one log exists per habit per tracking period.
This keeps the schema clean and avoids duplicating logic across multiple tables.
Beyond Binary Habit Tracking
Another limitation I noticed was that most applications force users into one of two tracking models:
- Check — Done / Not Done
- Count — Numeric progress toward a target
Neither worked well for many real-world habits.
Examples
Diet
Did you stick to your eating plan completely, make mostly good choices with a few slips, or abandon it for the day?
Reducing this to done or not done misses the nuance that actually matters for building consistency.
Learning
Did you finish your planned study session, get through half of it, or not open a book?
A binary result treats a half-session the same as no effort at all.
To better capture this kind of progress, I introduced a third tracking mode:
Scale Mode
Three-state tracking:
- Done
- Partial
- Not Done
These map to completion values of:
Done = 2
Partial = 1
Not Done = 0
This allows analytics to reflect partial consistency rather than collapsing it into failure, resulting in a more realistic representation of user behavior over time.
Motivation Through Visual Feedback
The application includes weekly and monthly progress overviews alongside GitHub-style contribution heatmaps.
Habit logs are transformed into visual completion levels, encouraging users to maintain streaks and fill gaps over time — making progress feel rewarding rather than administrative.
Instant Interactions with Optimistic Updates
The app uses optimistic updates, where the UI responds immediately before the database operation completes.
If the backend request fails, the interface automatically rolls back, providing a near-instant experience while maintaining consistency with the database.
User Ownership and Data Portability
I built a dedicated export pipeline through a Next.js backend service that converts habit history into CSV format, allowing users to download and migrate their data rather than being locked into the platform.
Administration & Analytics
The platform includes separate User and Admin roles, with an administrative dashboard for:
- Monitoring platform activity
- Managing users
- Viewing aggregated metrics
This required role-based authorization throughout both the frontend and backend.
Technical Highlights
- React Native mobile application with native Google Authentication
- Immutable habit versioning with
root_idlineage andversionedOutHabitscollection - Target snapshotting on habit logs preserving historical accuracy
- Unified
habit_logstable supporting daily, weekly, and monthly frequencies viaperiod_type - Active, Paused, and Archived habit lifecycle management
- Three input modes: Check, Scale, and Count
- GitHub-style heatmap visualizations
- Optimistic UI updates with automatic rollback
- Role-based User and Admin dashboards
- CSV export pipeline via Next.js for data portability
- PostgreSQL schema designed around analytics integrity and long-term history preservation