import { useCallback, useRef, useState } from "react";
import type { Nullable } from "@/types/nullable.ts";
interface CounterOptions {
interface CounterActions {
increment: (amount?: number) => void;
decrement: (amount?: number) => void;
set: (nextCount: number) => void;
reset: (to?: number) => void;
* Create a counter with increment, decrement, set, and reset actions.
* @param startingValue - The initial value of the counter
* @param options - The options for the counter
* const [count, { increment, decrement, set, reset }] = useCounter(5, { min: 0, max: 10 });
* <button onClick={increment}>Increment</button>
* <button onClick={decrement}>Decrement</button>
* <button onClick={() => set(5)}>Set to 5</button>
* <button onClick={() => reset()}>Reset</button>
export function useCounter(
startingValue: number = 0,
options: CounterOptions = {},
): [number, CounterActions] {
const { min, max } = options;
const initialValue = useRef(startingValue);
if (typeof min === "number" && typeof max === "number" && min > max) {
throw new Error(`Your min of ${min} is greater than your max of ${max}.`);
if (typeof min === "number" && startingValue < min) {
`Your starting value of ${startingValue} is less than your min of ${min}.`,
if (typeof max === "number" && startingValue > max) {
`Your starting value of ${startingValue} is greater than your max of ${max}.`,
const [count, setCount] = useState(startingValue);
const increment = useCallback(
(amount: number = 1) => {
const nextCount = c + amount;
if (typeof max === "number" && nextCount > max) {
const decrement = useCallback(
(amount: number = 1) => {
const nextCount = c - amount;
if (typeof min === "number" && nextCount < min) {
if (typeof max === "number" && nextCount > max) {
if (typeof min === "number" && nextCount < min) {
const reset = useCallback(
(to: number = initialValue.current) => {
if (typeof max === "number" && to > max) {
if (typeof min === "number" && to < min) {
if (initialValue.current !== to) {
initialValue.current = to;
return [count, { increment, decrement, set, reset }];