@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 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\";\r\n","import { useCallback, useState } from \"react\";\r\n\r\n/**\r\n * A simple counter hook\r\n * @param initialValue - Initial counter value (default: 0)\r\n */\r\nexport function useCounter(initialValue: number = 0) {\r\n const [count, setCount] = useState(initialValue);\r\n\r\n const increment = useCallback(() => {\r\n setCount((prev) => prev + 1);\r\n }, []);\r\n\r\n const decrement = useCallback(() => {\r\n setCount((prev) => prev - 1);\r\n }, []);\r\n\r\n const reset = useCallback(() => {\r\n setCount(initialValue);\r\n }, [initialValue]);\r\n\r\n return { count, increment, decrement, reset };\r\n}\r\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":[]}
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":[]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/useCounter.ts"],"sourcesContent":["import { useCallback, useState } from \"react\";\r\n\r\n/**\r\n * A simple counter hook\r\n * @param initialValue - Initial counter value (default: 0)\r\n */\r\nexport function useCounter(initialValue: number = 0) {\r\n const [count, setCount] = useState(initialValue);\r\n\r\n const increment = useCallback(() => {\r\n setCount((prev) => prev + 1);\r\n }, []);\r\n\r\n const decrement = useCallback(() => {\r\n setCount((prev) => prev - 1);\r\n }, []);\r\n\r\n const reset = useCallback(() => {\r\n setCount(initialValue);\r\n }, [initialValue]);\r\n\r\n return { count, increment, decrement, reset };\r\n}\r\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":[]}
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":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usefy/use-counter",
3
- "version": "0.0.7",
3
+ "version": "0.0.10",
4
4
  "description": "A React hook for counter state management",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",