Vize

use-counter

Create a clamped counter whose every transition stays inside [min, max].

Package @vizejs/composable/use-counter
Own the source vize lib pull composable:use-counter
Runtime exports useCounter
Gzip budget 1536 B

Minimal setup

Use a Vue 3.5+ project with Vue SFC compilation configured (see Vite+ integration).

vp install @vizejs/composable

Import the entry directly. Call lifecycle-bound utilities inside <script setup> so cleanup follows the component scope; consult the runtime contract for this entry's SSR and hydration behavior.

import { useCounter } from "@vizejs/composable/use-counter";

Try it

Reserve workshop seats

Keep a seat quantity between one and five and explain why further changes are unavailable.

Open the interactive example

What to observe: Add seats until the limit message appears. Add and remove controls disable at their respective bounds; Reset restores the initial one-seat reservation.

Mutate the readonly count through its controls. atMin and atMax are computed refs. Bounds are fixed when the composable is created.

Compare the initial and interacted states
Reserve workshop seats: initial state
Initial state
Reserve workshop seats: state after the verified interactions
After the verified interactions

Copy the example

Save this complete SFC in your Vue project. Its public imports and source are the same ones executed in the preview.

<script setup lang="ts">
import { useCounter } from "@vizejs/composable/use-counter";

const { count, atMin, atMax, increment, decrement, reset } = useCounter(1, { min: 1, max: 5 });
</script>

<template>
  <div class="composable-example">
    <p>Reserve up to five workshop seats.</p>
    <div class="example-actions">
      <button type="button" :disabled="atMin" @click="() => decrement()">Remove seat</button>
      <output aria-live="polite">{{ count }} {{ count === 1 ? "seat" : "seats" }}</output>
      <button type="button" :disabled="atMax" @click="() => increment()">Add seat</button>
      <button type="button" @click="() => reset()">Reset seats</button>
    </div>
    <p v-if="atMax">You reached the five-seat limit.</p>
  </div>
</template>

Runtime contract

Utility Category Stability SSR Hydration Cleanup Targets Host globals Uses
useCounter state experimental safe stable none web, server, worker, native, desktop, terminal — —

API

useCounter

Create a clamped counter whose every transition stays inside [min, max]. All operations clamp instead of failing, including the initial value, so the count is inside the bounds at every observable moment. Only NaN is rejected — silently corrupting the count is never an option. Purely synchronous state: safe during server rendering (no browser globals, no timers) and nothing to dispose, so it works inside and outside reactive scopes alike. Bounds are fixed at creation and not reactive.

function useCounter(initial = 0, options: UseCounterOptions = {}): CounterControls
const { count, increment, atMax } = useCounter(9, { min: 0, max: 10 });
increment(); // 10
increment(); // 10 (clamped)
atMax.value; // true

Types

UseCounterOptions

Options for useCounter.

Member Type Description
min? number Inclusive lower bound applied to every value the counter takes.
max? number Inclusive upper bound applied to every value the counter takes.

CounterControls

Reactive controls returned by useCounter.

Member Type Description
count Readonly<ShallowRef<number>> Current count. Changes only through the controls, never by assignment.
atMin ComputedRef<boolean> Whether the count currently sits on the configured lower bound.
atMax ComputedRef<boolean> Whether the count currently sits on the configured upper bound.
increment (delta?: number) => number Add delta (default 1) to the count and clamp into the bounds.
decrement (delta?: number) => number Subtract delta (default 1) from the count and clamp into the bounds.
set (value: number) => number Assign a value directly, clamped into the bounds.
reset (value?: number) => number Restore the reset baseline, or establish a new one. Without an argument the count returns to the creation-time initial value (after its original clamping). With an argument, the clamped value becomes both the new count and the baseline used by later reset() calls.