Introduction
To improve my logic-thinking and problem-solving skills, I decided to stop jumping directly into complex projects.
Instead, I started building small projects and improving them level by level, focusing more on logic than UI.
This article documents my journey of building a Multi-Counter Application in React, where each Git commit represents one learning.
The goal was simple:
Build logic incrementally, face real bugs, debug them, and learn deeply.
Project Overview
The Multi-Counter App allows users to:
- Increment and decrement counters
- Apply minimum and maximum constraints
- Disable buttons based on logic
Reset counters
Correctly handle multiple counters without shared-state bugs
Each feature was added as a new level, not all at once.
Tech Stack
- React.js
- JavaScript (ES6)
- Tailwind CSS
- Git & GitHub
Level-by-Level Development 🚀
Level 1: Increment and Decrement
Commit: Level 1: Increment and Decrement
started with a basic counter using useState.
const handleIncrement = () => setCount(count + 1);
const handleDecrement = () => setCount(count - 1);
Learning:
- How React state updates work
- How UI re-renders on state change
Level 2: Minimum Constraint & Disable Decrement Button
Commit: Level 2: Minimum Constraint and disabled button
I prevented the counter from going below 0 and disabled the decrement button.
Decrement
Learning:
- Handling edge cases
- Controlling UI with conditions
Level 3: Maximum Constraint & Disable Increment Button
Commit: Level 3: Maximum Constraint and Disable Increment Button
I added a maximum limit and disabled the increment button once the limit was reached.
Learning:
- Defensive programming
- Syncing UI with business logic
Level 4: Reset Functionality
Commit: Level 4: Reset Functionality
Added a reset button to bring the counter back to its initial value.
const handleReset = () => setCount(0);
Learning:
- Clean state reset patterns
- Better user experience
Level 8: Fix Increment Logic for Multiple Counters
Commit: Level 8: Fix increment logic for multiple counters
This was the most important level.
When I introduced multiple counters, I initially faced serious logic bugs:
- State updates behaved unpredictably
- Incrementing one counter affected others
Debugging counters.map is not a function in React
While working on the increment logic for multiple counters, I encountered a confusing runtime error:
counters.map is not a function
Initial Implementation
Initially, my handleIncrement function looked like this:
const handleDecrement = (id) => {
setCounters((prevCounters) => {
prevCounters.map((counter) => counter.id === id ? {...counter, value: counter.value > 0 ? counter.value -1 : counter.value} : counter)
})
}
The Issue
After the first click:
- The application threw counters.map is not a function
- Logging counter showed undefined
- The UI broke on the next render
Root Cause
The problem was a missing return statement.
Because I used curly braces {} in the arrow function passed to setCounters, JavaScript does not return anything by default.
As a result:
- he function returned undefined
- React set counters state to undefined
- On the next update, calling .map() caused the error
The Fix
I fixed the issue by explicitly returning the new array from map():
const handleDecrement = (id) => {
setCounters((prevCounters) => {
return prevCounters.map((counter) => counter.id === id ? {...counter, value: counter.value > 0 ? counter.value -1 : counter.value} : counter)
})
}
Why This Works
- map() returns a new array
- return ensures React receives valid state
- Only the clicked counter updates
- State remains immutable
Core Logic Explained
const handleIncrement = (id) => {
console.log("clicked")
setCounters((prevCounters) => {
console.log(prevCounters)
return prevCounters.map((counter) => counter.id === id ? {...counter, value: counter.value < 10 ? counter.value + 1 : counter.value} : counter)
})
}
This logic:
- Updates only one counter
- Keeps others unchanged
- Follows React’s immutability rules
Challenges Faced
- State becoming undefined
- Shared-state bugs with multiple counters
- Forgetting how arrow functions return values
What I Learned
- Most React bugs are JavaScript logic bugs
- Missing return can silently break state
- map is not a function usually means state is corrupted
- Small projects expose real-world issues
- Level-based learning builds confidence
Why I Used Level-Based Commits
- Each commit = one learning
- Easy to explain in interviews
- Clear progress tracking
- Meaningful GitHub activity
GitHub Repository
GitHub: https://github.com/Abhishek-Arankal/React-Counter
Final Thoughts
This project taught me one important lesson:
Logic improves through iteration, not complexity.
I’ll continue building small projects, documenting bugs, and learning in public.
If you see a better way to write this logic, I’d love your feedback.
SOCIAL SHARE CARD GENERATOR