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 [count, setCount] = useState(0)
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 when the value reached the minimum.
const handleDecrement = () => {
if(count > 0) {
setCount(count - 1)
}
}
<button disabled={count === 0}>Decrement</button>
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.
const handleIncrement = () => {
if(count < 10){
setCount(count + 1)
}
}
<button disabled={count < 10}>Increment</button>
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);
<button className='px-4 py-2 bg-yellow-600 rounded hover:bg-yellow-700 transition' onClick={() => setCount(0)}>Reset</button>
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:
❌ Runtime Error:
counters.map is not a function
Initial Implementation
Initially, my handleIncrement function looked like this:
const [counters, setCounters] = useState([
{id:1, value:0},
{id:2, value:0},
{id:3, value:0}
])
const handleIncrement = (id) => {
setCounters((prevCounters) => {
prevCounters.map((counter) => counter.id === id ? {...counter, value: counter.value < 10 ? counter.value + 1 : counter.value} : counter)
})
}
The Issue: counters.map is not a function
After clicking the increment or decrement button for the first time, the application crashed with the following error:
counters.map is not a function
This error did not happen during the click itself.
It occurred during the next render cycle, when React tried to re-render the UI.
What Happened Internally
- A button click triggered setCounters
- React updated the state
- React attempted to re-render the UI
- During rendering, this line executed:
counters.map(...) - At that moment, counters was no longer an array This caused the runtime error and broke the UI.
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:
- The 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 handleIncrement = (id) => {
setCounters((prevCounters) => {
return prevCounters.map((counter) => counter.id === id ? {...counter, value: counter.value < 10 ? 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) => {
setCounters((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.
Top comments (0)