FrontendAtlas
Interview Questions
Home>React interview questions> React coding challenges >React counter component with useState

Build a React Counter Component with useState

easy
By FrontendAtlas Editorial · Updated Aug 20, 2026
easy level
React useState Event handlers Derived disabled state
Build a React counter component with the useState hook. Initialize the count at zero, wire increment and guarded decrement handlers, disable decrement at zero, and keep the rendered value synchronized with state.
React useStateEvent handlersDerived disabled state

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

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.

1

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

Pitfalls
  • Updating the value without clamping, which allows negatives.
  • Recomputing from stale state instead of using the current value correctly.
Techniques
  • Using `useState` for local component state.
  • Guarding updates with `Math.max`.

Similar questions

Image Slider (Dots + Previous/Next)easyReact Chessboard Click/Highlight (N×N Board)easyReact Dynamic Table (Rows × Columns)easy

Guides

Frontend interview preparation guideGuideFrontend coding interview questions and prep guideBlueprintJavaScript Problems That Actually Show UpBlueprintBuild Great UI in 60 MinutesBlueprint

Preparing for interviews? Use Frontend Coding Challenges first, then move into a concrete Study Plan before targeted Company Prep.

Open frontend interview questionsBrowse React interview questionsOpen Essential 60Open Machine Coding HubOpen Frontend Coding ChallengesOpen System DesignOpen Interview Prep GuideOpen System Design BlueprintOpen Framework Prep PathsOpen Study PlansOpen 30-day foundations study planOpen Company PrepOpen React Framework Prep Guide
↗Incidents hub
20 / 28