DEV Community

Patrick Mbithi
Patrick Mbithi

Posted on

What a Calorie Counter Taught Me About Inputs I Didn't Know I Needed to Learn

A calorie counter sounds like one of those beginner projects that teaches you basic math and maybe some DOM stuff. Set a budget, log meals, subtract, done. I figured I'd knock it out in an afternoon.

The math part took twenty minutes. The rest of it was humbling.


Regex was the first wall

I'd seen regex before and mostly avoided it. This project forced me to actually write some.

The problem is user input. People type things like +200 or - 150 into calorie fields. Technically valid numbers. But if you pass those strings directly to Number(), you get NaN, which breaks everything quietly. The fix is a cleaning function that strips the noise before parsing:

function cleanInputString(str) {
  const regex = /[+-\s]/g;
  return str.replace(regex, '');
}
Enter fullscreen mode Exit fullscreen mode

Simple enough. But I had to actually understand why the + and - need to be inside the character class [] rather than sitting outside it, and what \s is catching, and why the g flag matters. None of that was hard once I looked it up. I just hadn't looked it up before.

The second regex was stranger. Browsers will accept scientific notation in a type="number" field. Type 1e10 and the input won't complain. Your app will then cheerfully try to add ten billion calories to breakfast. So there's a second check that specifically catches that pattern and rejects it:

function isInvalidInput(str) {
  const regex = /\d+e\d+/i;
  return str.match(regex);
}
Enter fullscreen mode Exit fullscreen mode

I would not have thought to look for that on my own. Good thing the curriculum did.


How functions handle failure

The project uses a global isError flag a boolean that resets to false at the start of every calculation, then flips to true inside getCaloriesFromInputs if bad input is found. The main calculation function checks it after gathering all the values and returns early if something went wrong.

It works fine. But sitting with it made me think about the other ways you could handle this. Return null and check for it upstream. Throw an error and catch it. Return an object with a status field. Each approach shifts where the responsibility sits.

I hadn't thought about that before. This project was the first time I had to actually notice the choice being made, even if I didn't make it myself.


Building the DOM on the fly

The "Add Entry" button injects new form fields into whatever meal category the user has selected. It does that with insertAdjacentHTML:

targetInputContainer.insertAdjacentHTML('beforeend', HTMLString);
Enter fullscreen mode Exit fullscreen mode

The HTML is a template literal with the entry number and category name baked into the id and for attributes. That's what keeps labels connected to their inputs as you keep adding rows. If those attributes don't match, clicking a label does nothing.

Before I saw this pattern I would have assumed you'd need a framework for dynamic forms. Turns out you don't. You just need to understand what insertAdjacentHTML does and why the attributes matter.


The clear function almost felt like a reward

After all of that, the form reset was almost relaxing. Loop through the input containers, set innerHTML to an empty string, clear the budget field, hide the output div. Four things. No edge cases. Done.

function clearForm() {
  const inputContainers = Array.from(document.querySelectorAll('.input-container'));
  for (const container of inputContainers) {
    container.innerHTML = '';
  }
  budgetNumberInput.value = '';
  output.innerText = '';
  output.classList.add('hide');
}
Enter fullscreen mode Exit fullscreen mode

I appreciated that.


What I actually took away

Going in, I thought this was a project about arithmetic and maybe some querySelector practice. What it actually drilled was input handling all the ways user input can be malformed, all the places where "valid" and "correct" are two different things, and how much of your validation logic exists specifically to close the gap between what the browser accepts and what your math can safely process.

That's probably obvious to experienced devs. For me, it was the thing I didn't know I was going to learn.

Top comments (0)