The Challenge
Today, I came across a challenge on hackerRank, focused on State management and it turned out to be interesting + to test my Basics!
Link to the HackerRank Challenge
Here are the details:
We have 5 different feedback cards labelled as:
- Readability
- Performance
- Security
- Documentation
- Testing
Each card has 2 buttons as Upvote and Downvote.
The Goal:
All cards should render together, but each card must manage its upvote/downvote count separately, without affecting the others. Along with it, they should pass the tests on HackerRank.
Use Case:
- Clicking Upvote on a card should increase that card’s upvote count by 1
- Clicking Downvote should increas that card’s downvote count by 1
Each card is independent, and the count should reflect it.
Let's explore the Solution:
Solution 1: Use 10 individual useState
Hooks.
Here, I’d maintain a separate state for:
- Each card’s upvotes (5 total)
- Each card’s downvotes (5 total)
const [readabilityUp, setReadabilityUp] = useState(0);
const [readabilityDown, setReadabilityDown] = useState(0);
// ...and so on for the other 4 cards
Pros:
- Simple to implement
- Easy to follow for beginners
Cons:
- Not scalable — 10 states for just 5 cards!
- Becomes messy with more feedback types
- Logic is duplicated across cards
Solution 2: Using useReducer
for centralised state
Instead of managing 10 different states, I centralized state handling using a reducer.
- Initial State:
const initialState = {
Readability: { upvote: 0, downvote: 0 },
Performance: { upvote: 0, downvote: 0 },
Security: { upvote: 0, downvote: 0 },
Documentation: { upvote: 0, downvote: 0 },
Testing: { upvote: 0, downvote: 0 },
}
- Reducer Logic:
function reducer(state, action) {
const { card, votetype } = action.payload;
switch (votetype) {
case "upvote":
return {
...state,
[card]: {
...state[card],
[votetype]: state[card][votetype] + 1
}
};
case "downvote":
return {
...state,
[card]: {
...state[card],
[votetype]: state[card][votetype] + 1
}
};
default:
return state;
}
}
Pros:
- Scalable and clean
- Centralised state handling
- DRY (Don't Repeat Yourself) principle applied
- Easier to debug and maintain
Cons:
- Slightly more advanced if you're new to React
- Requires understanding of reducers and dispatching actions
My Learning & Takeaways
I started with useState
, but then realised it isn't maintainable and Scalable. Once I refactored using useReducer
, the entire structure became cleaner, modular, scalable and better!
What I learned:
Think beyond “just getting it to work”, consider scalability and maintainability.
useReducer
is a great fit when managing multiple related states. Even simple UI components can challenge your architecture choices
Final Thoughts:
This small challenge helped me rethink how I approach state in React.
It was more than just fixing a UI; it became an exercise in writing cleaner, scalable code.
Would love to hear how you would have solved this! Let’s discuss 💬
Top comments (1)
Ah yes, the eternal React journey: start with useState because it feels friendly, then suddenly you’re buried under 10 states like an overworked intern juggling sticky notes. Switch to useReducer, and boom—you’ve basically written a mini Redux but without the corporate trauma. Next step: accidentally reinventing React Query just to track upvotes