@alessiofrittoli/react-hooks 1.0.0

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.
Files changed (99) hide show
  1. package/README.md +830 -0
  2. package/dist/browser-api/index.d.mts +6 -0
  3. package/dist/browser-api/index.d.ts +6 -0
  4. package/dist/browser-api/index.js +2 -0
  5. package/dist/browser-api/index.js.map +1 -0
  6. package/dist/browser-api/index.mjs +2 -0
  7. package/dist/browser-api/index.mjs.map +1 -0
  8. package/dist/browser-api/storage/index.d.mts +4 -0
  9. package/dist/browser-api/storage/index.d.ts +4 -0
  10. package/dist/browser-api/storage/index.js +2 -0
  11. package/dist/browser-api/storage/index.js.map +1 -0
  12. package/dist/browser-api/storage/index.mjs +2 -0
  13. package/dist/browser-api/storage/index.mjs.map +1 -0
  14. package/dist/browser-api/storage/useLocalStorage.d.mts +11 -0
  15. package/dist/browser-api/storage/useLocalStorage.d.ts +11 -0
  16. package/dist/browser-api/storage/useLocalStorage.js +2 -0
  17. package/dist/browser-api/storage/useLocalStorage.js.map +1 -0
  18. package/dist/browser-api/storage/useLocalStorage.mjs +2 -0
  19. package/dist/browser-api/storage/useLocalStorage.mjs.map +1 -0
  20. package/dist/browser-api/storage/useSessionStorage.d.mts +11 -0
  21. package/dist/browser-api/storage/useSessionStorage.d.ts +11 -0
  22. package/dist/browser-api/storage/useSessionStorage.js +2 -0
  23. package/dist/browser-api/storage/useSessionStorage.js.map +1 -0
  24. package/dist/browser-api/storage/useSessionStorage.mjs +2 -0
  25. package/dist/browser-api/storage/useSessionStorage.mjs.map +1 -0
  26. package/dist/browser-api/storage/useStorage.d.mts +12 -0
  27. package/dist/browser-api/storage/useStorage.d.ts +12 -0
  28. package/dist/browser-api/storage/useStorage.js +2 -0
  29. package/dist/browser-api/storage/useStorage.js.map +1 -0
  30. package/dist/browser-api/storage/useStorage.mjs +2 -0
  31. package/dist/browser-api/storage/useStorage.mjs.map +1 -0
  32. package/dist/browser-api/useIsPortrait.d.mts +10 -0
  33. package/dist/browser-api/useIsPortrait.d.ts +10 -0
  34. package/dist/browser-api/useIsPortrait.js +2 -0
  35. package/dist/browser-api/useIsPortrait.js.map +1 -0
  36. package/dist/browser-api/useIsPortrait.mjs +2 -0
  37. package/dist/browser-api/useIsPortrait.mjs.map +1 -0
  38. package/dist/browser-api/useMediaQuery.d.mts +11 -0
  39. package/dist/browser-api/useMediaQuery.d.ts +11 -0
  40. package/dist/browser-api/useMediaQuery.js +2 -0
  41. package/dist/browser-api/useMediaQuery.js.map +1 -0
  42. package/dist/browser-api/useMediaQuery.mjs +2 -0
  43. package/dist/browser-api/useMediaQuery.mjs.map +1 -0
  44. package/dist/dom-api/index.d.mts +2 -0
  45. package/dist/dom-api/index.d.ts +2 -0
  46. package/dist/dom-api/index.js +2 -0
  47. package/dist/dom-api/index.js.map +1 -0
  48. package/dist/dom-api/index.mjs +2 -0
  49. package/dist/dom-api/index.mjs.map +1 -0
  50. package/dist/dom-api/useFocusTrap.d.mts +15 -0
  51. package/dist/dom-api/useFocusTrap.d.ts +15 -0
  52. package/dist/dom-api/useFocusTrap.js +2 -0
  53. package/dist/dom-api/useFocusTrap.js.map +1 -0
  54. package/dist/dom-api/useFocusTrap.mjs +2 -0
  55. package/dist/dom-api/useFocusTrap.mjs.map +1 -0
  56. package/dist/dom-api/useScrollBlock.d.mts +8 -0
  57. package/dist/dom-api/useScrollBlock.d.ts +8 -0
  58. package/dist/dom-api/useScrollBlock.js +2 -0
  59. package/dist/dom-api/useScrollBlock.js.map +1 -0
  60. package/dist/dom-api/useScrollBlock.mjs +2 -0
  61. package/dist/dom-api/useScrollBlock.mjs.map +1 -0
  62. package/dist/eslint.d.mts +11 -0
  63. package/dist/eslint.d.ts +11 -0
  64. package/dist/eslint.js +2 -0
  65. package/dist/eslint.js.map +1 -0
  66. package/dist/eslint.mjs +2 -0
  67. package/dist/eslint.mjs.map +1 -0
  68. package/dist/index.d.mts +11 -0
  69. package/dist/index.d.ts +11 -0
  70. package/dist/index.js +2 -0
  71. package/dist/index.js.map +1 -0
  72. package/dist/index.mjs +2 -0
  73. package/dist/index.mjs.map +1 -0
  74. package/dist/misc/index.d.mts +3 -0
  75. package/dist/misc/index.d.ts +3 -0
  76. package/dist/misc/index.js +2 -0
  77. package/dist/misc/index.js.map +1 -0
  78. package/dist/misc/index.mjs +2 -0
  79. package/dist/misc/index.mjs.map +1 -0
  80. package/dist/misc/useIsClient.d.mts +8 -0
  81. package/dist/misc/useIsClient.d.ts +8 -0
  82. package/dist/misc/useIsClient.js +2 -0
  83. package/dist/misc/useIsClient.js.map +1 -0
  84. package/dist/misc/useIsClient.mjs +2 -0
  85. package/dist/misc/useIsClient.mjs.map +1 -0
  86. package/dist/misc/useIsFirstRender.d.mts +9 -0
  87. package/dist/misc/useIsFirstRender.d.ts +9 -0
  88. package/dist/misc/useIsFirstRender.js +2 -0
  89. package/dist/misc/useIsFirstRender.js.map +1 -0
  90. package/dist/misc/useIsFirstRender.mjs +2 -0
  91. package/dist/misc/useIsFirstRender.mjs.map +1 -0
  92. package/dist/misc/useUpdateEffect.d.mts +9 -0
  93. package/dist/misc/useUpdateEffect.d.ts +9 -0
  94. package/dist/misc/useUpdateEffect.js +2 -0
  95. package/dist/misc/useUpdateEffect.js.map +1 -0
  96. package/dist/misc/useUpdateEffect.mjs +2 -0
  97. package/dist/misc/useUpdateEffect.mjs.map +1 -0
  98. package/license.md +21 -0
  99. package/package.json +144 -0
package/README.md ADDED
@@ -0,0 +1,830 @@
1
+ # React Hooks 🪝
2
+
3
+ [![NPM Latest Version][version-badge]][npm-url] [![Coverage Status][coverage-badge]][coverage-url] [![Socket Status][socket-badge]][socket-url] [![NPM Monthly Downloads][downloads-badge]][npm-url] [![Dependencies][deps-badge]][deps-url]
4
+
5
+ [![GitHub Sponsor][sponsor-badge]][sponsor-url]
6
+
7
+ [version-badge]: https://img.shields.io/npm/v/%40alessiofrittoli%2Freact-hooks
8
+ [npm-url]: https://npmjs.org/package/%40alessiofrittoli%2Freact-hooks
9
+ [coverage-badge]: https://coveralls.io/repos/github/alessiofrittoli/react-hooks/badge.svg
10
+ [coverage-url]: https://coveralls.io/github/alessiofrittoli/react-hooks
11
+ [socket-badge]: https://socket.dev/api/badge/npm/package/@alessiofrittoli/react-hooks
12
+ [socket-url]: https://socket.dev/npm/package/@alessiofrittoli/react-hooks/overview
13
+ [downloads-badge]: https://img.shields.io/npm/dm/%40alessiofrittoli%2Freact-hooks.svg
14
+ [deps-badge]: https://img.shields.io/librariesio/release/npm/%40alessiofrittoli%2Freact-hooks
15
+ [deps-url]: https://libraries.io/npm/%40alessiofrittoli%2Freact-hooks
16
+
17
+ [sponsor-badge]: https://img.shields.io/static/v1?label=Fund%20this%20package&message=%E2%9D%A4&logo=GitHub&color=%23DB61A2
18
+ [sponsor-url]: https://github.com/sponsors/alessiofrittoli
19
+
20
+ ## TypeScript React utility Hooks
21
+
22
+ ### Table of Contents
23
+
24
+ - [Getting started](#getting-started)
25
+ - [ESLint Configuration](#eslint-configuration)
26
+ - [API Reference](#api-reference)
27
+ - [Browser API](#browser-api)
28
+ - [DOM API](#dom-api)
29
+ - [Miscellaneous](#miscellaneous)
30
+ - [Development](#development)
31
+ - [Install depenendencies](#install-depenendencies)
32
+ - [Build the source code](#build-the-source-code)
33
+ - [ESLint](#eslint)
34
+ - [Jest](#jest)
35
+ - [Contributing](#contributing)
36
+ - [Security](#security)
37
+ - [Credits](#made-with-)
38
+
39
+ ---
40
+
41
+ ### Getting started
42
+
43
+ Run the following command to start using `react-hooks` in your projects:
44
+
45
+ ```bash
46
+ npm i @alessiofrittoli/react-hooks
47
+ ```
48
+
49
+ or using `pnpm`
50
+
51
+ ```bash
52
+ pnpm i @alessiofrittoli/react-hooks
53
+ ```
54
+
55
+ ---
56
+
57
+ ### ESLint Configuration
58
+
59
+ This library may define and exports hooks that requires additional ESLint configuration for your project such as [`useUpdateEffect`](#useupdateeffect).
60
+
61
+ Simply imports recommended configuration from `@alessiofrittoli/react-hooks/eslint` and add them to your ESLint configuration like so:
62
+
63
+ ```mjs
64
+ import { config as AFReactHooksEslint } from '@alessiofrittoli/react-hooks/eslint'
65
+
66
+ /** @type {import('eslint').Linter.Config[]} */
67
+ const config = [
68
+ ...AFReactHooksEslint.recommended,
69
+ // ... other configurations
70
+ ]
71
+
72
+
73
+ export default config
74
+ ```
75
+
76
+ ---
77
+
78
+ ### API Reference
79
+
80
+ #### Browser API
81
+
82
+ ##### Storage
83
+
84
+ The following storage hooks use Storage Utilities from [`@alessiofrittoli/web-utils`](https://npmjs.com/package/@alessiofrittoli/web-utils#storage-utilities) adding a React oriented implementation.
85
+
86
+ ###### `useStorage`
87
+
88
+ Easly handle Local or Session Storage State.
89
+
90
+ <details>
91
+
92
+ <summary style="cursor:pointer">Type parameters</summary>
93
+
94
+ | Parameter | Type | Default | Description |
95
+ |-----------|------|---------|-------------|
96
+ | `T` | `any` | `string` | A custom type applied to the stored item. |
97
+
98
+ </details>
99
+
100
+ ---
101
+
102
+ <details>
103
+
104
+ <summary style="cursor:pointer">Parameters</summary>
105
+
106
+ | Parameter | Type | Default | Description |
107
+ |-----------|------|---------|-------------|
108
+ | `key` | `string` | - | The storage item key. |
109
+ | `initialValue` | `T` | - | The storage item initial value. |
110
+ | `type` | `local\|session` | local | (Optional) The storage API to use. |
111
+
112
+ </details>
113
+
114
+ ---
115
+
116
+ <details>
117
+
118
+ <summary style="cursor:pointer">Returns</summary>
119
+
120
+ Type: `[ Value<T>, SetValue<Value<T>> ]`
121
+
122
+ A tuple with the stored item value or initial value and the setter function.
123
+
124
+ </details>
125
+
126
+ ---
127
+
128
+ <details>
129
+
130
+ <summary style="cursor:pointer">Usage</summary>
131
+
132
+ ###### Importing the hooks
133
+
134
+ ```tsx
135
+ import {
136
+ useStorage, useLocalStorage, useSessionStorage
137
+ } from '@alessiofrittoli/react-hooks'
138
+ // or
139
+ import {
140
+ useStorage, useLocalStorage, useSessionStorage
141
+ } from '@alessiofrittoli/react-hooks/browser-api'
142
+ // or
143
+ import {
144
+ useStorage, useLocalStorage, useSessionStorage
145
+ } from '@alessiofrittoli/react-hooks/browser-api/storage'
146
+ ```
147
+
148
+ ---
149
+
150
+ ###### Reading item value from storage
151
+
152
+ ```tsx
153
+ 'use client'
154
+
155
+ import { useStorage } from '@alessiofrittoli/react-hooks/browser-api/storage'
156
+
157
+ type Locale = 'it' | 'en'
158
+
159
+ const storage = 'local' // or 'session'
160
+ const defaultLocale = 'it'
161
+
162
+ export const SomeComponent: React.FC = () => {
163
+
164
+ const [ userLocale ] = useStorage<Locale>( 'user-locale', defaultLocale, storage )
165
+
166
+ return (
167
+ ...
168
+ )
169
+
170
+ }
171
+ ```
172
+
173
+ ---
174
+
175
+ ###### Updating storage item value
176
+
177
+ ```tsx
178
+ 'use client'
179
+
180
+ import { useCallback } from 'react'
181
+ import { useStorage } from '@alessiofrittoli/react-hooks/browser-api/storage'
182
+
183
+ type Locale = 'it' | 'en'
184
+
185
+ const storage = 'local' // or 'session'
186
+ const defaultLocale = 'it'
187
+
188
+ export const LanguageSwitcher: React.FC = () => {
189
+
190
+ const [ userLocale, setUserLocale ] = useStorage<Locale>( 'user-locale', defaultLocale, storage )
191
+
192
+ const clickHandler = useCallback( () => {
193
+ setUserLocale( 'en' )
194
+ }, [ setUserLocale ] )
195
+
196
+ return (
197
+ ...
198
+ )
199
+
200
+ }
201
+ ```
202
+
203
+ ---
204
+
205
+ ###### Deleting storage item
206
+
207
+ ```tsx
208
+ 'use client'
209
+
210
+ import { useCallback } from 'react'
211
+ import { useStorage } from '@alessiofrittoli/react-hooks/browser-api/storage'
212
+
213
+ type Locale = 'it' | 'en'
214
+
215
+ const storage = 'local' // or 'session'
216
+ const defaultLocale = 'it'
217
+
218
+ export const LanguageSwitcher: React.FC = () => {
219
+
220
+ const [ userLocale, setUserLocale ] = useStorage<Locale>( 'user-locale', defaultLocale, storage )
221
+
222
+ const deleteHandler = useCallback( () => {
223
+ setUserLocale( null )
224
+ // or
225
+ setUserLocale( undefined )
226
+ // or
227
+ setUserLocale( '' )
228
+ }, [ setUserLocale ] )
229
+
230
+ return (
231
+ ...
232
+ )
233
+
234
+ }
235
+ ```
236
+
237
+ </details>
238
+
239
+ ---
240
+
241
+ ###### `useLocalStorage`
242
+
243
+ Shortcut React Hook for [`useStorage`](#usestorage).
244
+
245
+ Applies the same API Reference.
246
+
247
+ ---
248
+
249
+ ###### `useSessionStorage`
250
+
251
+ Shortcut React Hook for [`useStorage`](#usestorage).
252
+
253
+ Applies the same API Reference.
254
+
255
+ ---
256
+
257
+ ##### `useMediaQuery`
258
+
259
+ Get Document Media matches and listen for changes.
260
+
261
+ <details>
262
+
263
+ <summary style="cursor:pointer">Parameters</summary>
264
+
265
+ | Parameter | Type | Description |
266
+ |-----------|----------|-------------|
267
+ | `query` | `string` | A string specifying the media query to parse into a `MediaQueryList`. |
268
+
269
+ </details>
270
+
271
+ ---
272
+
273
+ <details>
274
+
275
+ <summary style="cursor:pointer">Returns</summary>
276
+
277
+ Type: `boolean`
278
+
279
+ - `true` if the document currently matches the media query list.
280
+ - `false` otherwise.
281
+
282
+ </details>
283
+
284
+ ---
285
+
286
+ <details>
287
+
288
+ <summary style="cursor:pointer">Usage</summary>
289
+
290
+ ###### Importing the hook
291
+
292
+ ```tsx
293
+ import { useMediaQuery } from '@alessiofrittoli/react-hooks'
294
+ // or
295
+ import { useMediaQuery } from '@alessiofrittoli/react-hooks/browser-api'
296
+ ```
297
+
298
+ ---
299
+
300
+ ###### Check if user device prefers dark color scheme
301
+
302
+ ```tsx
303
+ const isDarkOS = useMediaQuery( '(prefers-color-scheme: dark)' )
304
+ ```
305
+
306
+ </details>
307
+
308
+ ---
309
+
310
+ ##### `useIsPortrait`
311
+
312
+ Check if device is portrait oriented.
313
+
314
+ React State get updated when device orientation changes.
315
+
316
+ <details>
317
+
318
+ <summary style="cursor:pointer">Returns</summary>
319
+
320
+ Type: `boolean`
321
+
322
+ - `true` if the device is portrait oriented.
323
+ - `false` otherwise.
324
+
325
+ </details>
326
+
327
+ ---
328
+
329
+ <details>
330
+
331
+ <summary style="cursor:pointer">Usage</summary>
332
+
333
+ ###### Importing the hook
334
+
335
+ ```tsx
336
+ import { useIsPortrait } from '@alessiofrittoli/react-hooks'
337
+ // or
338
+ import { useIsPortrait } from '@alessiofrittoli/react-hooks/browser-api'
339
+ ```
340
+
341
+ ---
342
+
343
+ ###### Check if user device is in landscape
344
+
345
+ ```tsx
346
+ const isLandscape = ! useIsPortrait()
347
+ ```
348
+
349
+ </details>
350
+
351
+ ---
352
+
353
+ #### DOM API
354
+
355
+ ##### `useScrollBlock`
356
+
357
+ Prevent Element overflow.
358
+
359
+ <details>
360
+
361
+ <summary style="cursor:pointer">Parameters</summary>
362
+
363
+ | Parameter | Type | Default | Description |
364
+ |-----------|------|---------|-------------|
365
+ | `target` | `React.RefObject<HTMLElement\|null>` | `Document.documentElement` | (Optional) The React RefObject target HTMLElement. |
366
+
367
+ </details>
368
+
369
+ ---
370
+
371
+ <details>
372
+
373
+ <summary style="cursor:pointer">Returns</summary>
374
+
375
+ Type: `[ () => void, () => void ]`
376
+
377
+ A tuple with block and restore scroll callbacks.
378
+
379
+ </details>
380
+
381
+ ---
382
+
383
+ <details>
384
+
385
+ <summary style="cursor:pointer">Usage</summary>
386
+
387
+ ###### Importing the hook
388
+
389
+ ```tsx
390
+ import { useScrollBlock } from '@alessiofrittoli/react-hooks'
391
+ // or
392
+ import { useScrollBlock } from '@alessiofrittoli/react-hooks/dom-api'
393
+ ```
394
+
395
+ ---
396
+
397
+ ###### Block Document Overflow
398
+
399
+ ```tsx
400
+ const [ blockScroll, restoreScroll ] = useScrollBlock()
401
+
402
+ const openPopUpHandler = useCallback( () => {
403
+ ...
404
+ blockScroll()
405
+ }, [ blockScroll ] )
406
+
407
+ const closePopUpHandler = useCallback( () => {
408
+ ...
409
+ restoreScroll()
410
+ }, [ restoreScroll ] )
411
+
412
+ ...
413
+ ```
414
+
415
+ ---
416
+
417
+ ###### Block HTML Element Overflow
418
+
419
+ ```tsx
420
+ const elementRef = useRef<HTMLDivElement>( null )
421
+
422
+ const [ blockScroll, restoreScroll ] = useScrollBlock( elementRef )
423
+
424
+ const scrollBlockHandler = useCallback( () => {
425
+ ...
426
+ blockScroll()
427
+ }, [ blockScroll ] )
428
+
429
+ const scrollRestoreHandler = useCallback( () => {
430
+ ...
431
+ restoreScroll()
432
+ }, [ restoreScroll ] )
433
+
434
+ ...
435
+ ```
436
+
437
+ </details>
438
+
439
+ ---
440
+
441
+ ##### `useFocusTrap`
442
+
443
+ Trap focus inside the given HTML Element.
444
+
445
+ This comes pretty handy when rendering a modal that shouldn't be closed without a user required action.
446
+
447
+ <details>
448
+
449
+ <summary style="cursor:pointer">Parameters</summary>
450
+
451
+ | Parameter | Type | Description |
452
+ |-----------|------|-------------|
453
+ | `target` | `React.RefObject<HTMLElement\|null>` | The target HTMLElement React RefObject to trap focus within. |
454
+ | | | If no target is given, you must provide the target HTMLElement when calling `setFocusTrap`. |
455
+
456
+ </details>
457
+
458
+ ---
459
+
460
+ <details>
461
+
462
+ <summary style="cursor:pointer">Returns</summary>
463
+
464
+ Type: `readonly [ SetFocusTrap, RestoreFocusTrap ]`
465
+
466
+ A tuple containing:
467
+
468
+ - `setFocusTrap`: A function to enable the focus trap. Optionally accept an HTMLElement as target.
469
+ - `restoreFocusTrap`: A function to restore the previous focus state.
470
+
471
+ </details>
472
+
473
+ ---
474
+
475
+ <details>
476
+
477
+ <summary style="cursor:pointer">Usage</summary>
478
+
479
+ ###### Importing the hook
480
+
481
+ ```tsx
482
+ import { useFocusTrap } from '@alessiofrittoli/react-hooks'
483
+ // or
484
+ import { useFocusTrap } from '@alessiofrittoli/react-hooks/dom-api'
485
+ ```
486
+
487
+ ---
488
+
489
+ ###### Defining the target on hook initialization
490
+
491
+ ```tsx
492
+ const modalRef = useRef<HTMLDivElement>( null )
493
+ const [ setFocusTrap, restoreFocusTrap ] = useFocusTrap( modalRef )
494
+
495
+ const modalOpenHandler = useCallback( () => {
496
+ if ( ! modalRef.current ) return
497
+ // ... open modal
498
+ setFocusTrap()
499
+ modalRef.current.focus() // focus the dialog so next tab will focus the next element inside the modal
500
+ }, [ setFocusTrap ] )
501
+
502
+ const modalCloseHandler = useCallback( () => {
503
+ // ... close modal
504
+ restoreFocusTrap() // cancel focus trap and restore focus to the last active element before enablig the focus trap
505
+ }, [ restoreFocusTrap ] )
506
+ ```
507
+
508
+ ---
509
+
510
+ ###### Defining the target ondemand
511
+
512
+ ```tsx
513
+ const modalRef = useRef<HTMLDivElement>( null )
514
+ const modal2Ref = useRef<HTMLDivElement>( null )
515
+ const [ setFocusTrap, restoreFocusTrap ] = useFocusTrap()
516
+
517
+ const modalOpenHandler = useCallback( () => {
518
+ if ( ! modalRef.current ) return
519
+ // ... open modal
520
+ setFocusTrap( modalRef.current )
521
+ modalRef.current.focus()
522
+ }, [ setFocusTrap ] )
523
+
524
+ const modal2OpenHandler = useCallback( () => {
525
+ if ( ! modal2Ref.current ) return
526
+ // ... open modal
527
+ setFocusTrap( modal2Ref.current )
528
+ modal2Ref.current.focus()
529
+ }, [ setFocusTrap ] )
530
+ ```
531
+
532
+ </details>
533
+
534
+ ---
535
+
536
+ #### Miscellaneous
537
+
538
+ ##### `useIsClient`
539
+
540
+ Check if the React Hook or Component where this hook is executed is running in a browser environment.
541
+
542
+ This is pretty usefull to avoid hydration errors.
543
+
544
+ <details>
545
+
546
+ <summary style="cursor:pointer">Returns</summary>
547
+
548
+ Type: `boolean`
549
+
550
+ - `true` if the React Hook or Component is running in a browser environment.
551
+ - `false` otherwise.
552
+
553
+ </details>
554
+
555
+ ---
556
+
557
+ <details>
558
+
559
+ <summary style="cursor:pointer">Usage</summary>
560
+
561
+ ###### Importing the hook
562
+
563
+ ```tsx
564
+ import { useIsClient } from '@alessiofrittoli/react-hooks'
565
+ // or
566
+ import { useIsClient } from '@alessiofrittoli/react-hooks/misc'
567
+ ```
568
+
569
+ ###### Basic usage
570
+
571
+ ```tsx
572
+ 'use client'
573
+
574
+ import { useIsClient } from '@alessiofrittoli/react-hooks/misc'
575
+
576
+ export const ClientComponent: React.FC = () => {
577
+
578
+ const isClient = useIsClient()
579
+
580
+ return (
581
+ <div>Running { ! isClient ? 'server' : 'client' }-side</div>
582
+ )
583
+
584
+ }
585
+ ```
586
+
587
+ </details>
588
+
589
+ ---
590
+
591
+ ##### `useIsFirstRender`
592
+
593
+ Check if is first React Hook/Component render.
594
+
595
+ <details>
596
+
597
+ <summary style="cursor:pointer">Returns</summary>
598
+
599
+ Type: `boolean`
600
+
601
+ - `true` at the mount time.
602
+ - `false` otherwise.
603
+
604
+ Note that if the React Hook/Component has no state updates, `useIsFirstRender` will always return `true`.
605
+
606
+ </details>
607
+
608
+ ---
609
+
610
+ <details>
611
+
612
+ <summary style="cursor:pointer">Usage</summary>
613
+
614
+ ###### Importing the hook
615
+
616
+ ```tsx
617
+ import { useIsFirstRender } from '@alessiofrittoli/react-hooks'
618
+ // or
619
+ import { useIsFirstRender } from '@alessiofrittoli/react-hooks/misc'
620
+ ```
621
+
622
+ ###### Basic usage
623
+
624
+ ```tsx
625
+ 'use client'
626
+
627
+ import { useIsFirstRender } from '@alessiofrittoli/react-hooks/misc'
628
+
629
+ export const ClientComponent: React.FC = () => {
630
+
631
+ const isFirstRender = useIsFirstRender()
632
+ const [ counter, setCounter ] = useState( 0 )
633
+
634
+ useEffect( () => {
635
+ const intv = setInterval( () => {
636
+ setCounter( prev => prev + 1 )
637
+ }, 1000 )
638
+ return () => clearInterval( intv )
639
+ }, [] )
640
+
641
+ return (
642
+ <div>
643
+ { isFirstRender ? 'First render' : 'Subsequent render' }
644
+ <hr />
645
+ { counter }
646
+ </div>
647
+ )
648
+
649
+ }
650
+ ```
651
+
652
+ </details>
653
+
654
+ ---
655
+
656
+ ##### `useUpdateEffect`
657
+
658
+ Modified version of `useEffect` that skips the first render.
659
+
660
+ <details>
661
+
662
+ <summary style="cursor:pointer">Parameters</summary>
663
+
664
+ | Parameter | Type | Description |
665
+ |-----------|------------------------|-------------|
666
+ | `effect` | `React.EffectCallback` | Imperative function that can return a cleanup function. |
667
+ | `deps` | `React.DependencyList` | If present, effect will only activate if the values in the list change. |
668
+
669
+ </details>
670
+
671
+ ---
672
+
673
+ <details>
674
+
675
+ <summary style="cursor:pointer">Usage</summary>
676
+
677
+ ###### Importing the hook
678
+
679
+ ```tsx
680
+ import { useUpdateEffect } from '@alessiofrittoli/react-hooks'
681
+ // or
682
+ import { useUpdateEffect } from '@alessiofrittoli/react-hooks/misc'
683
+ ```
684
+
685
+ ###### Basic usage
686
+
687
+ ```tsx
688
+ 'use client'
689
+
690
+ import { useEffect, useState } from 'react'
691
+ import { useUpdateEffect } from '@alessiofrittoli/react-hooks/misc'
692
+
693
+ export const ClientComponent: React.FC = () => {
694
+
695
+ const [ count, setCount ] = useState( 0 )
696
+
697
+ useEffect( () => {
698
+ const intv = setInterval( () => {
699
+ setCount( prev => prev + 1 )
700
+ }, 1000 )
701
+ return () => clearInterval( intv )
702
+ }, [] )
703
+
704
+ useEffect( () => {
705
+ console.log( 'useEffect', count ) // starts from 0
706
+ return () => {
707
+ console.log( 'useEffect - clean up', count ) // starts from 0
708
+ }
709
+ }, [ count ] )
710
+
711
+ useUpdateEffect( () => {
712
+ console.log( 'useUpdateEffect', count ) // starts from 1
713
+ return () => {
714
+ console.log( 'useUpdateEffect - clean up', count ) // starts from 1
715
+ }
716
+ }, [ count ] )
717
+
718
+ return (
719
+ <div>{ count }</div>
720
+ )
721
+
722
+ }
723
+ ```
724
+
725
+ </details>
726
+
727
+ ---
728
+
729
+ ### Development
730
+
731
+ #### Install depenendencies
732
+
733
+ ```bash
734
+ npm install
735
+ ```
736
+
737
+ or using `pnpm`
738
+
739
+ ```bash
740
+ pnpm i
741
+ ```
742
+
743
+ #### Build the source code
744
+
745
+ Run the following command to test and build code for distribution.
746
+
747
+ ```bash
748
+ pnpm build
749
+ ```
750
+
751
+ #### [ESLint](https://www.npmjs.com/package/eslint)
752
+
753
+ warnings / errors check.
754
+
755
+ ```bash
756
+ pnpm lint
757
+ ```
758
+
759
+ #### [Jest](https://npmjs.com/package/jest)
760
+
761
+ Run all the defined test suites by running the following:
762
+
763
+ ```bash
764
+ # Run tests and watch file changes.
765
+ pnpm test:watch
766
+
767
+ # Run tests in a CI environment.
768
+ pnpm test:ci
769
+ ```
770
+
771
+ - See [`package.json`](./package.json) file scripts for more info.
772
+
773
+ Run tests with coverage.
774
+
775
+ An HTTP server is then started to serve coverage files from `./coverage` folder.
776
+
777
+ ⚠️ You may see a blank page the first time you run this command. Simply refresh the browser to see the updates.
778
+
779
+ ```bash
780
+ test:coverage:serve
781
+ ```
782
+
783
+ ---
784
+
785
+ ### Contributing
786
+
787
+ Contributions are truly welcome!
788
+
789
+ Please refer to the [Contributing Doc](./CONTRIBUTING.md) for more information on how to start contributing to this project.
790
+
791
+ Help keep this project up to date with [GitHub Sponsor][sponsor-url].
792
+
793
+ [![GitHub Sponsor][sponsor-badge]][sponsor-url]
794
+
795
+ ---
796
+
797
+ ### Security
798
+
799
+ If you believe you have found a security vulnerability, we encourage you to **_responsibly disclose this and NOT open a public issue_**. We will investigate all legitimate reports. Email `security@alessiofrittoli.it` to disclose any security vulnerabilities.
800
+
801
+ ### Made with ☕
802
+
803
+ <table style='display:flex;gap:20px;'>
804
+ <tbody>
805
+ <tr>
806
+ <td>
807
+ <img alt="avatar" src='https://avatars.githubusercontent.com/u/35973186' style='width:60px;border-radius:50%;object-fit:contain;'>
808
+ </td>
809
+ <td>
810
+ <table style='display:flex;gap:2px;flex-direction:column;'>
811
+ <tbody>
812
+ <tr>
813
+ <td>
814
+ <a href='https://github.com/alessiofrittoli' target='_blank' rel='noopener'>Alessio Frittoli</a>
815
+ </td>
816
+ </tr>
817
+ <tr>
818
+ <td>
819
+ <small>
820
+ <a href='https://alessiofrittoli.it' target='_blank' rel='noopener'>https://alessiofrittoli.it</a> |
821
+ <a href='mailto:info@alessiofrittoli.it' target='_blank' rel='noopener'>info@alessiofrittoli.it</a>
822
+ </small>
823
+ </td>
824
+ </tr>
825
+ </tbody>
826
+ </table>
827
+ </td>
828
+ </tr>
829
+ </tbody>
830
+ </table>