Build a React Counter Component with useState
Requirements
Render a <Counter /> as the main UI. Display the current count value. Provide '+' and '-' buttons to increment and decrement. Prevent the count from ever going below 0.
Expected behavior
Clicking '+' increases the count by 1. Clicking '-' decreases the count by 1, but never below 0. Initial count starts at 0.
Implementation notes
Initialize local component state with useState(0). Guard the decrement so it never sets a negative value. Disable decrement at zero so the interface communicates the invariant. Keep everything in a single small component for this exercise.
FAQ
How do you build a counter component in React with useState?
Call useState(0) to store the count, render that state, and update it from increment and decrement button handlers. Guard decrement so the value cannot fall below zero.
How do you prevent a React counter from going below zero?
Guard the decrement so it clamps at zero, then disable the decrement button when count is zero to keep behavior and visual state aligned.
Solution idea
A single count state drives the UI. Increment adds 1, decrement subtracts 1 but clamps at 0. Disabling the minus button at zero communicates the rule to users and prevents invalid negative values. This is a clean example of state-driven UI and controlled updates. Performance & pitfalls: consider edge cases (empty state, rapid clicks), trade-offs in state shape, and accessibility of interactive controls.
Solution
Overview
A single count state drives the UI. Increment adds 1, decrement subtracts 1 but clamps at 0. Disabling the minus button at zero communicates the rule to users and prevents invalid negative values. This is a clean example of state-driven UI and controlled updates. Performance & pitfalls: consider edge cases (empty state, rapid clicks), trade-offs in state shape, and accessibility of interactive controls.
Single component with guarded decrement
Initialize count with useState(0). Implement inc as setCount(c => c + 1) and dec as setCount(c => Math.max(0, c - 1)). Bind the decrement button’s disabled prop when count === 0, and render the count in the UI so every click immediately reflects the new state. Edge cases & testing: cover invalid inputs, ensure predictable reactivity, and keep the UI stable under rapid updates.
Files
{
"name": "react-counter",
"version": "0.1.0",
"private": true,
"dependencies": {
"react": "18.x",
"react-dom": "18.x",
"react-scripts": "5.0.1"
},
"scripts": { "start": "react-scripts start" }
}
{
"compilerOptions": {
"jsx": "react-jsx",
"target": "ES2020",
"module": "ESNext",
"strict": true
}
}
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>React SDK • Counter</title></head><body><div id="root"></div></body></html>:root {
color-scheme: dark;
}
html, body {
margin: 0;
height: 100%;
background: #0a0a0a;
font-family: system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif;
}
.main {
min-height: 100vh;
background: #0a0a0a;
color: #e5e7eb;
display: grid;
place-items: center;
padding: 24px;
}
.section {
width: 100%;
max-width: 420px;
display: grid;
gap: 16px;
}
h1 {
margin: 0;
font-size: 20px;
font-weight: 700;
}
.panel {
background: #0b1020;
border: 1px solid #1f2937;
border-radius: 12px;
padding: 16px;
}
.value {
font-size: 48px;
font-weight: 800;
text-align: center;
margin-bottom: 12px;
}
.actions {
display: flex;
gap: 8px;
justify-content: center;
}
button {
background: #a3e635;
color: #111;
border: none;
border-radius: 10px;
padding: 10px 14px;
font-weight: 700;
cursor: pointer;
}
button[disabled] {
opacity: .5;
cursor: not-allowed;
}
button.ghost {
background: transparent;
color: #e5e7eb;
border: 1px solid #374151;
}
import React, { useState } from 'react';
import './App.css';
export default function App() {
const [count, setCount] = useState(0);
const isZero = count === 0;
const inc = () => {
setCount((c) => c + 1);
};
const dec = () => {
setCount((c) => (c > 0 ? c - 1 : 0));
};
const reset = () => {
setCount(0);
};
return (
<main className="main">
<section className="section">
<h1>Counter</h1>
<div className="panel">
<div className="value">{count}</div>
<div className="actions">
<button onClick={dec} disabled={isZero}>−</button>
<button onClick={inc}>+</button>
<button onClick={reset} className="ghost">Reset</button>
</div>
</div>
</section>
</main>
);
}
import React from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';
createRoot(document.getElementById('root')!).render(<App />);
Notes & Pitfalls
- Updating the value without clamping, which allows negatives.
- Recomputing from stale state instead of using the current value correctly.
- Using `useState` for local component state.
- Guarding updates with `Math.max`.
Similar questions
Guides
Preparing for interviews? Use Frontend Coding Challenges first, then move into a concrete Study Plan before targeted Company Prep.
Built in public
The code behind FrontendAtlas lives on GitHub. A star helps other frontend engineers find it.