@usefy/use-counter 0.0.7 → 0.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +430 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/geon0529/usefy/master/assets/logo.png" alt="usefy logo" width="120" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<h1 align="center">@usefy/use-counter</h1>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<strong>A lightweight, type-safe React hook for counter state management</strong>
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
<p align="center">
|
|
12
|
+
<a href="https://www.npmjs.com/package/@usefy/use-counter">
|
|
13
|
+
<img src="https://img.shields.io/npm/v/@usefy/use-counter.svg?style=flat-square&color=007acc" alt="npm version" />
|
|
14
|
+
</a>
|
|
15
|
+
<a href="https://www.npmjs.com/package/@usefy/use-counter">
|
|
16
|
+
<img src="https://img.shields.io/npm/dm/@usefy/use-counter.svg?style=flat-square&color=007acc" alt="npm downloads" />
|
|
17
|
+
</a>
|
|
18
|
+
<a href="https://bundlephobia.com/package/@usefy/use-counter">
|
|
19
|
+
<img src="https://img.shields.io/bundlephobia/minzip/@usefy/use-counter?style=flat-square&color=007acc" alt="bundle size" />
|
|
20
|
+
</a>
|
|
21
|
+
<a href="https://github.com/geon0529/usefy/blob/master/LICENSE">
|
|
22
|
+
<img src="https://img.shields.io/npm/l/@usefy/use-counter.svg?style=flat-square&color=007acc" alt="license" />
|
|
23
|
+
</a>
|
|
24
|
+
</p>
|
|
25
|
+
|
|
26
|
+
<p align="center">
|
|
27
|
+
<a href="#installation">Installation</a> •
|
|
28
|
+
<a href="#quick-start">Quick Start</a> •
|
|
29
|
+
<a href="#api-reference">API Reference</a> •
|
|
30
|
+
<a href="#examples">Examples</a> •
|
|
31
|
+
<a href="#license">License</a>
|
|
32
|
+
</p>
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Overview
|
|
37
|
+
|
|
38
|
+
`@usefy/use-counter` is a simple yet powerful React hook for managing counter state. It provides a clean, intuitive API with stable function references, making it ideal for building increment/decrement UIs, pagination controls, quantity selectors, and more.
|
|
39
|
+
|
|
40
|
+
**Part of the [@usefy](https://www.npmjs.com/org/usefy) ecosystem** — a collection of production-ready React hooks designed for modern applications.
|
|
41
|
+
|
|
42
|
+
### Why use-counter?
|
|
43
|
+
|
|
44
|
+
- **Zero Dependencies** — Pure React implementation with no external dependencies
|
|
45
|
+
- **TypeScript First** — Full type safety with comprehensive type definitions
|
|
46
|
+
- **Stable References** — Functions are memoized with `useCallback` for optimal performance
|
|
47
|
+
- **SSR Compatible** — Works seamlessly with Next.js, Remix, and other SSR frameworks
|
|
48
|
+
- **Lightweight** — Minimal bundle footprint (~200B minified + gzipped)
|
|
49
|
+
- **Well Tested** — Comprehensive test coverage with Vitest
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Installation
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
# npm
|
|
57
|
+
npm install @usefy/use-counter
|
|
58
|
+
|
|
59
|
+
# yarn
|
|
60
|
+
yarn add @usefy/use-counter
|
|
61
|
+
|
|
62
|
+
# pnpm
|
|
63
|
+
pnpm add @usefy/use-counter
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Peer Dependencies
|
|
67
|
+
|
|
68
|
+
This package requires React 18 or 19:
|
|
69
|
+
|
|
70
|
+
```json
|
|
71
|
+
{
|
|
72
|
+
"peerDependencies": {
|
|
73
|
+
"react": "^18.0.0 || ^19.0.0"
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Quick Start
|
|
81
|
+
|
|
82
|
+
```tsx
|
|
83
|
+
import { useCounter } from '@usefy/use-counter';
|
|
84
|
+
|
|
85
|
+
function Counter() {
|
|
86
|
+
const { count, increment, decrement, reset } = useCounter(0);
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
<div>
|
|
90
|
+
<p>Count: {count}</p>
|
|
91
|
+
<button onClick={increment}>+</button>
|
|
92
|
+
<button onClick={decrement}>-</button>
|
|
93
|
+
<button onClick={reset}>Reset</button>
|
|
94
|
+
</div>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## API Reference
|
|
102
|
+
|
|
103
|
+
### `useCounter(initialValue?)`
|
|
104
|
+
|
|
105
|
+
A hook that manages counter state with increment, decrement, and reset capabilities.
|
|
106
|
+
|
|
107
|
+
#### Parameters
|
|
108
|
+
|
|
109
|
+
| Parameter | Type | Default | Description |
|
|
110
|
+
|-----------|------|---------|-------------|
|
|
111
|
+
| `initialValue` | `number` | `0` | The initial value of the counter |
|
|
112
|
+
|
|
113
|
+
#### Returns
|
|
114
|
+
|
|
115
|
+
| Property | Type | Description |
|
|
116
|
+
|----------|------|-------------|
|
|
117
|
+
| `count` | `number` | The current counter value |
|
|
118
|
+
| `increment` | `() => void` | Increases the counter by 1 |
|
|
119
|
+
| `decrement` | `() => void` | Decreases the counter by 1 |
|
|
120
|
+
| `reset` | `() => void` | Resets the counter to the initial value |
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## Examples
|
|
125
|
+
|
|
126
|
+
### Basic Counter
|
|
127
|
+
|
|
128
|
+
```tsx
|
|
129
|
+
import { useCounter } from '@usefy/use-counter';
|
|
130
|
+
|
|
131
|
+
function BasicCounter() {
|
|
132
|
+
const { count, increment, decrement, reset } = useCounter();
|
|
133
|
+
|
|
134
|
+
return (
|
|
135
|
+
<div className="counter">
|
|
136
|
+
<span className="count">{count}</span>
|
|
137
|
+
<div className="controls">
|
|
138
|
+
<button onClick={decrement} aria-label="Decrease">−</button>
|
|
139
|
+
<button onClick={increment} aria-label="Increase">+</button>
|
|
140
|
+
<button onClick={reset} aria-label="Reset">Reset</button>
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Starting with a Custom Value
|
|
148
|
+
|
|
149
|
+
```tsx
|
|
150
|
+
import { useCounter } from '@usefy/use-counter';
|
|
151
|
+
|
|
152
|
+
function QuantitySelector() {
|
|
153
|
+
const { count, increment, decrement } = useCounter(1);
|
|
154
|
+
|
|
155
|
+
return (
|
|
156
|
+
<div className="quantity-selector">
|
|
157
|
+
<button
|
|
158
|
+
onClick={decrement}
|
|
159
|
+
disabled={count <= 1}
|
|
160
|
+
aria-label="Decrease quantity"
|
|
161
|
+
>
|
|
162
|
+
−
|
|
163
|
+
</button>
|
|
164
|
+
<span aria-label="Quantity">{count}</span>
|
|
165
|
+
<button
|
|
166
|
+
onClick={increment}
|
|
167
|
+
aria-label="Increase quantity"
|
|
168
|
+
>
|
|
169
|
+
+
|
|
170
|
+
</button>
|
|
171
|
+
</div>
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Pagination Control
|
|
177
|
+
|
|
178
|
+
```tsx
|
|
179
|
+
import { useCounter } from '@usefy/use-counter';
|
|
180
|
+
|
|
181
|
+
function Pagination({ totalPages }: { totalPages: number }) {
|
|
182
|
+
const { count: currentPage, increment: nextPage, decrement: prevPage, reset } = useCounter(1);
|
|
183
|
+
|
|
184
|
+
return (
|
|
185
|
+
<nav className="pagination" aria-label="Pagination">
|
|
186
|
+
<button
|
|
187
|
+
onClick={prevPage}
|
|
188
|
+
disabled={currentPage <= 1}
|
|
189
|
+
>
|
|
190
|
+
Previous
|
|
191
|
+
</button>
|
|
192
|
+
<span>Page {currentPage} of {totalPages}</span>
|
|
193
|
+
<button
|
|
194
|
+
onClick={nextPage}
|
|
195
|
+
disabled={currentPage >= totalPages}
|
|
196
|
+
>
|
|
197
|
+
Next
|
|
198
|
+
</button>
|
|
199
|
+
<button onClick={reset}>First Page</button>
|
|
200
|
+
</nav>
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### Multiple Independent Counters
|
|
206
|
+
|
|
207
|
+
```tsx
|
|
208
|
+
import { useCounter } from '@usefy/use-counter';
|
|
209
|
+
|
|
210
|
+
function ScoreBoard() {
|
|
211
|
+
const teamA = useCounter(0);
|
|
212
|
+
const teamB = useCounter(0);
|
|
213
|
+
|
|
214
|
+
const resetAll = () => {
|
|
215
|
+
teamA.reset();
|
|
216
|
+
teamB.reset();
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
return (
|
|
220
|
+
<div className="scoreboard">
|
|
221
|
+
<div className="team">
|
|
222
|
+
<h3>Team A</h3>
|
|
223
|
+
<span className="score">{teamA.count}</span>
|
|
224
|
+
<button onClick={teamA.increment}>+1</button>
|
|
225
|
+
</div>
|
|
226
|
+
<div className="team">
|
|
227
|
+
<h3>Team B</h3>
|
|
228
|
+
<span className="score">{teamB.count}</span>
|
|
229
|
+
<button onClick={teamB.increment}>+1</button>
|
|
230
|
+
</div>
|
|
231
|
+
<button onClick={resetAll}>Reset Game</button>
|
|
232
|
+
</div>
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### With Negative Values
|
|
238
|
+
|
|
239
|
+
```tsx
|
|
240
|
+
import { useCounter } from '@usefy/use-counter';
|
|
241
|
+
|
|
242
|
+
function TemperatureAdjuster() {
|
|
243
|
+
const { count: temperature, increment, decrement, reset } = useCounter(-10);
|
|
244
|
+
|
|
245
|
+
return (
|
|
246
|
+
<div className="temperature">
|
|
247
|
+
<span>{temperature}°C</span>
|
|
248
|
+
<button onClick={increment}>Warmer</button>
|
|
249
|
+
<button onClick={decrement}>Cooler</button>
|
|
250
|
+
<button onClick={reset}>Reset to -10°C</button>
|
|
251
|
+
</div>
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
|
|
258
|
+
## TypeScript
|
|
259
|
+
|
|
260
|
+
This hook is written in TypeScript and provides full type inference out of the box.
|
|
261
|
+
|
|
262
|
+
```tsx
|
|
263
|
+
import { useCounter } from '@usefy/use-counter';
|
|
264
|
+
|
|
265
|
+
// Return type is automatically inferred
|
|
266
|
+
const { count, increment, decrement, reset } = useCounter(0);
|
|
267
|
+
|
|
268
|
+
// count: number
|
|
269
|
+
// increment: () => void
|
|
270
|
+
// decrement: () => void
|
|
271
|
+
// reset: () => void
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
---
|
|
275
|
+
|
|
276
|
+
## Performance
|
|
277
|
+
|
|
278
|
+
The hook uses `useCallback` to memoize all returned functions, ensuring stable references across re-renders. This is particularly important when passing these functions as props to child components or using them as dependencies in other hooks.
|
|
279
|
+
|
|
280
|
+
```tsx
|
|
281
|
+
const { increment, decrement, reset } = useCounter(0);
|
|
282
|
+
|
|
283
|
+
// These references remain stable across renders
|
|
284
|
+
useEffect(() => {
|
|
285
|
+
// Safe to use as dependencies
|
|
286
|
+
}, [increment, decrement, reset]);
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
---
|
|
290
|
+
|
|
291
|
+
## Testing
|
|
292
|
+
|
|
293
|
+
This package maintains comprehensive test coverage to ensure reliability and stability.
|
|
294
|
+
|
|
295
|
+
### Test Coverage
|
|
296
|
+
|
|
297
|
+
| Category | Tests | Coverage |
|
|
298
|
+
|----------|-------|----------|
|
|
299
|
+
| Initialization | 5 | 100% |
|
|
300
|
+
| Increment | 4 | 100% |
|
|
301
|
+
| Decrement | 4 | 100% |
|
|
302
|
+
| Reset | 5 | 100% |
|
|
303
|
+
| Complex Scenarios | 7 | 100% |
|
|
304
|
+
| Function References | 1 | 100% |
|
|
305
|
+
| **Total** | **26** | **100%** |
|
|
306
|
+
|
|
307
|
+
### Test Categories
|
|
308
|
+
|
|
309
|
+
<details>
|
|
310
|
+
<summary><strong>Initialization Tests</strong></summary>
|
|
311
|
+
|
|
312
|
+
- Default value initialization (0)
|
|
313
|
+
- Custom initial value
|
|
314
|
+
- Negative initial value
|
|
315
|
+
- Large number support
|
|
316
|
+
- Explicit zero initialization
|
|
317
|
+
|
|
318
|
+
</details>
|
|
319
|
+
|
|
320
|
+
<details>
|
|
321
|
+
<summary><strong>Increment Tests</strong></summary>
|
|
322
|
+
|
|
323
|
+
- Increment from zero
|
|
324
|
+
- Increment from positive values
|
|
325
|
+
- Increment from negative values
|
|
326
|
+
- Multiple consecutive increments
|
|
327
|
+
|
|
328
|
+
</details>
|
|
329
|
+
|
|
330
|
+
<details>
|
|
331
|
+
<summary><strong>Decrement Tests</strong></summary>
|
|
332
|
+
|
|
333
|
+
- Decrement from positive values
|
|
334
|
+
- Decrement crossing zero boundary
|
|
335
|
+
- Decrement from negative values
|
|
336
|
+
- Multiple consecutive decrements
|
|
337
|
+
|
|
338
|
+
</details>
|
|
339
|
+
|
|
340
|
+
<details>
|
|
341
|
+
<summary><strong>Reset Tests</strong></summary>
|
|
342
|
+
|
|
343
|
+
- Reset after increment operations
|
|
344
|
+
- Reset after decrement operations
|
|
345
|
+
- Reset to negative initial value
|
|
346
|
+
- Reset to zero
|
|
347
|
+
- Multiple reset operations
|
|
348
|
+
|
|
349
|
+
</details>
|
|
350
|
+
|
|
351
|
+
<details>
|
|
352
|
+
<summary><strong>Complex Scenario Tests</strong></summary>
|
|
353
|
+
|
|
354
|
+
- Sequential mixed operations
|
|
355
|
+
- Zero boundary crossing
|
|
356
|
+
- State stability verification
|
|
357
|
+
- Alternating increment/decrement
|
|
358
|
+
- Large number operations
|
|
359
|
+
- Multiple independent hook instances
|
|
360
|
+
- Function reference stability across renders
|
|
361
|
+
|
|
362
|
+
</details>
|
|
363
|
+
|
|
364
|
+
### Running Tests
|
|
365
|
+
|
|
366
|
+
```bash
|
|
367
|
+
# Run all tests
|
|
368
|
+
pnpm test
|
|
369
|
+
|
|
370
|
+
# Run tests in watch mode
|
|
371
|
+
pnpm test:watch
|
|
372
|
+
|
|
373
|
+
# Run tests with coverage report
|
|
374
|
+
pnpm test --coverage
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
### Testing Stack
|
|
378
|
+
|
|
379
|
+
- **[Vitest](https://vitest.dev/)** — Fast, modern test runner
|
|
380
|
+
- **[@testing-library/react](https://testing-library.com/react)** — React testing utilities
|
|
381
|
+
- **[jsdom](https://github.com/jsdom/jsdom)** — DOM environment for Node.js
|
|
382
|
+
|
|
383
|
+
---
|
|
384
|
+
|
|
385
|
+
## Related Packages
|
|
386
|
+
|
|
387
|
+
Explore other hooks in the **@usefy** collection:
|
|
388
|
+
|
|
389
|
+
| Package | Description |
|
|
390
|
+
|---------|-------------|
|
|
391
|
+
| [@usefy/use-toggle](https://www.npmjs.com/package/@usefy/use-toggle) | Boolean state management |
|
|
392
|
+
| [@usefy/use-debounce](https://www.npmjs.com/package/@usefy/use-debounce) | Value debouncing |
|
|
393
|
+
| [@usefy/use-debounce-callback](https://www.npmjs.com/package/@usefy/use-debounce-callback) | Debounced callbacks |
|
|
394
|
+
| [@usefy/use-throttle](https://www.npmjs.com/package/@usefy/use-throttle) | Value throttling |
|
|
395
|
+
| [@usefy/use-throttle-callback](https://www.npmjs.com/package/@usefy/use-throttle-callback) | Throttled callbacks |
|
|
396
|
+
| [@usefy/use-click-any-where](https://www.npmjs.com/package/@usefy/use-click-any-where) | Global click detection |
|
|
397
|
+
|
|
398
|
+
---
|
|
399
|
+
|
|
400
|
+
## Contributing
|
|
401
|
+
|
|
402
|
+
We welcome contributions! Please see our [Contributing Guide](https://github.com/geon0529/usefy/blob/master/CONTRIBUTING.md) for details.
|
|
403
|
+
|
|
404
|
+
```bash
|
|
405
|
+
# Clone the repository
|
|
406
|
+
git clone https://github.com/geon0529/usefy.git
|
|
407
|
+
|
|
408
|
+
# Install dependencies
|
|
409
|
+
pnpm install
|
|
410
|
+
|
|
411
|
+
# Run tests
|
|
412
|
+
pnpm test
|
|
413
|
+
|
|
414
|
+
# Build
|
|
415
|
+
pnpm build
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
---
|
|
419
|
+
|
|
420
|
+
## License
|
|
421
|
+
|
|
422
|
+
MIT © [mirunamu](https://github.com/geon0529)
|
|
423
|
+
|
|
424
|
+
This package is part of the [usefy](https://github.com/geon0529/usefy) monorepo.
|
|
425
|
+
|
|
426
|
+
---
|
|
427
|
+
|
|
428
|
+
<p align="center">
|
|
429
|
+
<sub>Built with care by the usefy team</sub>
|
|
430
|
+
</p>
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/useCounter.ts"],"sourcesContent":["export { useCounter } from \"./useCounter\";\
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/useCounter.ts"],"sourcesContent":["export { useCounter } from \"./useCounter\";\n","import { useCallback, useState } from \"react\";\n\n/**\n * A simple counter hook\n * @param initialValue - Initial counter value (default: 0)\n */\nexport function useCounter(initialValue: number = 0) {\n const [count, setCount] = useState(initialValue);\n\n const increment = useCallback(() => {\n setCount((prev) => prev + 1);\n }, []);\n\n const decrement = useCallback(() => {\n setCount((prev) => prev - 1);\n }, []);\n\n const reset = useCallback(() => {\n setCount(initialValue);\n }, [initialValue]);\n\n return { count, increment, decrement, reset };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,mBAAsC;AAM/B,SAAS,WAAW,eAAuB,GAAG;AACnD,QAAM,CAAC,OAAO,QAAQ,QAAI,uBAAS,YAAY;AAE/C,QAAM,gBAAY,0BAAY,MAAM;AAClC,aAAS,CAAC,SAAS,OAAO,CAAC;AAAA,EAC7B,GAAG,CAAC,CAAC;AAEL,QAAM,gBAAY,0BAAY,MAAM;AAClC,aAAS,CAAC,SAAS,OAAO,CAAC;AAAA,EAC7B,GAAG,CAAC,CAAC;AAEL,QAAM,YAAQ,0BAAY,MAAM;AAC9B,aAAS,YAAY;AAAA,EACvB,GAAG,CAAC,YAAY,CAAC;AAEjB,SAAO,EAAE,OAAO,WAAW,WAAW,MAAM;AAC9C;","names":[]}
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/useCounter.ts"],"sourcesContent":["import { useCallback, useState } from \"react\";\
|
|
1
|
+
{"version":3,"sources":["../src/useCounter.ts"],"sourcesContent":["import { useCallback, useState } from \"react\";\n\n/**\n * A simple counter hook\n * @param initialValue - Initial counter value (default: 0)\n */\nexport function useCounter(initialValue: number = 0) {\n const [count, setCount] = useState(initialValue);\n\n const increment = useCallback(() => {\n setCount((prev) => prev + 1);\n }, []);\n\n const decrement = useCallback(() => {\n setCount((prev) => prev - 1);\n }, []);\n\n const reset = useCallback(() => {\n setCount(initialValue);\n }, [initialValue]);\n\n return { count, increment, decrement, reset };\n}\n"],"mappings":";AAAA,SAAS,aAAa,gBAAgB;AAM/B,SAAS,WAAW,eAAuB,GAAG;AACnD,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAS,YAAY;AAE/C,QAAM,YAAY,YAAY,MAAM;AAClC,aAAS,CAAC,SAAS,OAAO,CAAC;AAAA,EAC7B,GAAG,CAAC,CAAC;AAEL,QAAM,YAAY,YAAY,MAAM;AAClC,aAAS,CAAC,SAAS,OAAO,CAAC;AAAA,EAC7B,GAAG,CAAC,CAAC;AAEL,QAAM,QAAQ,YAAY,MAAM;AAC9B,aAAS,YAAY;AAAA,EACvB,GAAG,CAAC,YAAY,CAAC;AAEjB,SAAO,EAAE,OAAO,WAAW,WAAW,MAAM;AAC9C;","names":[]}
|