Skip to main content

Overview

The useCounter hook provides a straightforward way to manage counter state with built-in operations for incrementing, decrementing, and resetting the value. It demonstrates the fundamental pattern of creating custom hooks in React.

Purpose

This hook encapsulates counter logic that would otherwise be repeated across components. It’s ideal for:
  • Stepper components
  • Quantity selectors in e-commerce apps
  • Pagination controls
  • Teaching basic custom hook patterns

API Reference

Hook Signature

useCounter(initialValue?: number): UseCounterReturn

Parameters

initialValue
number
default:"10"
The starting value for the counter. If not provided, defaults to 10.

Return Value

The hook returns an object with the following properties:
counter
number
required
The current counter value
handleAdd
() => void
required
Function to increment the counter by 1
handleSubtract
() => void
required
Function to decrement the counter by 1
handleReset
() => void
required
Function to reset the counter to 5 (hardcoded reset value)

Implementation Details

The hook uses React’s useState to manage the counter state. Here’s the complete implementation:
import { useState } from "react";

export const useCounter = (initialValue: number = 10) => {
  const [counter, setCounter] = useState(initialValue);
  
  const handleAdd = () => {
    setCounter(counter + 1);
  };
  
  const handleSubtract = () => {
    setCounter((prevState) => prevState - 1);
  };

  const handleReset = () => {
    setCounter(5);
  };
  
  return {
    // Values
    counter,
    // Methods / Actions
    handleAdd,
    handleSubtract,
    handleReset,
  };
};

Key Implementation Details

The hook accepts an optional initialValue parameter with a default of 10. This value is passed directly to useState to set the initial counter state.
handleAdd uses direct state value: setCounter(counter + 1). While this works for synchronous updates, it’s not optimal for rapid consecutive updates.
handleReset resets the counter to a hardcoded value of 5, not the initialValue. This could be modified to reset to initialValue if needed.

Usage Examples

Basic Usage

MyCounterApp.tsx
import { useCounter } from "../hooks/useCounter";

export const MyCounterApp = () => {
  const { counter, handleAdd, handleSubtract, handleReset } = useCounter(5);
  
  return (
    <div style={{ 
      display: "flex", 
      flexDirection: "column", 
      alignItems: "center" 
    }}>
      <h1>counter: {counter}</h1>
      <div style={{ display: "flex", gap: "10px" }}>
        <button onClick={handleAdd}>+1</button>
        <button onClick={handleSubtract}>-1</button>
        <button onClick={handleReset}>Reset</button>
      </div>
    </div>
  );
};

Advanced Usage

QuantitySelector.tsx
import { useCounter } from "../hooks/useCounter";

interface QuantitySelectorProps {
  min?: number;
  max?: number;
  onQuantityChange?: (quantity: number) => void;
}

export const QuantitySelector = ({ 
  min = 1, 
  max = 99,
  onQuantityChange 
}: QuantitySelectorProps) => {
  const { counter, handleAdd, handleSubtract } = useCounter(min);
  
  const handleIncrement = () => {
    if (counter < max) {
      handleAdd();
      onQuantityChange?.(counter + 1);
    }
  };
  
  const handleDecrement = () => {
    if (counter > min) {
      handleSubtract();
      onQuantityChange?.(counter - 1);
    }
  };
  
  return (
    <div className="quantity-selector">
      <button 
        onClick={handleDecrement} 
        disabled={counter <= min}
      >
        -
      </button>
      <span>{counter}</span>
      <button 
        onClick={handleIncrement} 
        disabled={counter >= max}
      >
        +
      </button>
    </div>
  );
};

Potential Improvements

The current implementation has a few areas that could be enhanced:
Consider using the functional updater pattern for all state updates:
const handleAdd = () => {
  setCounter((prevState) => prevState + 1);
};
Make the reset value configurable:
const handleReset = () => {
  setCounter(initialValue); // Reset to initial instead of hardcoded 5
};
Allow custom increment/decrement amounts:
const handleAdd = (step: number = 1) => {
  setCounter((prev) => prev + step);
};
Add optional min/max constraints:
export const useCounter = (
  initialValue: number = 10,
  { min, max }: { min?: number; max?: number } = {}
) => {
  // Add boundary checks in handlers
};

useGifs

Learn about managing async state and caching

useWeather

Explore complex state management with multiple effects

Build docs developers (and LLMs) love