DEV Community

forkcount
forkcount

Posted on

I built a free calorie tracker with a metabolic safety floor. No card, no email, no paywall.

Every calorie app I tried had the same problem: it let you set a target of 1,200 calories or lower and called it a plan. Some went lower. That's how people end up in the binge-starve cycle — you eat 800, your body fights back, you eat 4,000, you hate yourself, repeat.

So I built one that refuses.

What it does

ForkCount asks your height, weight, age, gender, and activity level. Then it calculates your BMR (Mifflin-St Jeor), your TDEE (BMR × activity multiplier), and a target that keeps you above the BMR floor. If you try to set a target below that line, it won't let you. You can override it in the settings, but the app tells you what you're doing and shows the maths.

The rest is a food diary, water tracker, weight log, exercise log, weekly meal plan generator, and a community feed. You type "200g chicken and 150g rice" and it parses the food. There's a barcode scanner, weight tracking, exercise logging with a kcal estimate.

Free. No card. No trial. No paywall. Signup is a username and password — no email required.

Stack

React + Vite + TypeScript

Firebase Firestore for the database

Firebase Auth for accounts

GitHub Pages for hosting (deployed from the docs/ folder, not the repo root)

No backend. All logic is client-side.

The hardest part: Firestore rejects undefined

The bug that cost me the most time was this:

text
Function setDoc() called with invalid data. Unsupported field value: undefined
Firestore refuses any document that contains an undefined value anywhere inside it. Not null — undefined. And when it fails, it doesn't tell you which field, just the document path. So if you're saving a nested object with 40 fields, you're searching for a needle in a haystack.

The fix was a recursive cleaner, routed through every write in the app:

js
function cleanForFirestore(obj) {
if (Array.isArray(obj)) return obj.map(cleanForFirestore);
if (obj instanceof Date) return obj;
if (obj && typeof obj === 'object') {
const out = {};
for (const k in obj) {
if (obj[k] !== undefined) out[k] = cleanForFirestore(obj[k]);
}
return out;
}
return obj;
}
Then I wrapped setDoc, updateDoc, and addDoc in a safeFirestore helper that cleans the data before every write. One import change, and no write in the app can send undefined to Firestore again.

The other lesson: the deploy settings, not the code

The site broke for two days with this:

text
Failed to load module script: Expected a JavaScript-or-Wasm module script but the server responded with a MIME type of "application/octet-stream"
I was staring at the code. The problem was in GitHub Pages settings. It was serving my repo root — which has /src/main.tsx — instead of my built dist/ folder. The fix was to copy dist/ into a docs/ folder, point Pages at /docs, and stop letting it serve source.

The error message was the browser refusing to run a TypeScript file as JavaScript. Once you know that, it makes sense.

What I'd like feedback on

Does the onboarding flow make sense the first time you see it? It's country → height/weight/age → fitness level → goal.

Is the calorie calculation right? Mifflin-St Jeor for BMR, activity multiplier for TDEE, 300–500 deficit, floor at BMR. I want someone who actually knows nutrition math to sanity-check it.

Does it break on your phone? I've tested on one device and I need more.

forkcount.github.io

I'm 13 and I built this by directing Google AI Studio, not by writing code by hand. So if something looks like a beginner mistake, it probably is. Tell me and I'll fix it.

Top comments (0)