@alessiofrittoli/react-hooks 3.3.0-alpha.1 → 3.3.0-alpha.3

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 CHANGED
@@ -23,23 +23,37 @@
23
23
 
24
24
  - [Getting started](#getting-started)
25
25
  - [ESLint Configuration](#eslint-configuration)
26
+ - [What's Changed](#whats-changed)
26
27
  - [API Reference](#api-reference)
27
28
  - [Browser API](#browser-api)
28
29
  - [`useStorage`](#usestorage)
29
30
  - [`useLocalStorage`](#uselocalstorage)
30
31
  - [`useSessionStorage`](#usesessionstorage)
31
- - [`useMediaQuery`](#usemediaquery)
32
+ - [`useConnection`](#useconnection)
32
33
  - [`useDarkMode`](#usedarkmode)
34
+ - [`useEventListener`](#useeventlistener)
33
35
  - [`useIsPortrait`](#useisportrait)
36
+ - [`useMediaQuery`](#usemediaquery)
34
37
  - [DOM API](#dom-api)
35
- - [`useScrollBlock`](#usescrollblock)
36
38
  - [`useFocusTrap`](#usefocustrap)
37
39
  - [`useInView`](#useinview)
40
+ - [`useScrollBlock`](#usescrollblock)
38
41
  - [Miscellaneous](#miscellaneous)
42
+ - [`useInput`](#useinput)
43
+ - [`useDeferCallback`](#usedefercallback)
44
+ - [`useEffectOnce`](#useeffectonce)
45
+ - [`useUpdateEffect`](#useupdateeffect)
39
46
  - [`useIsClient`](#useisclient)
40
47
  - [`useIsFirstRender`](#useisfirstrender)
41
- - [`useUpdateEffect`](#useupdateeffect)
42
48
  - [`usePagination`](#usepagination)
49
+ - [`useSelection`](#useselection)
50
+ - [Timers](#timers)
51
+ - [`useDebounce`](#usedebounce)
52
+ - [`useInterval`](#useinterval)
53
+ - [`useIntervalWhenVisible`](#useintervalwhenvisible)
54
+ - [`useLightInterval`](#uselightinterval)
55
+ - [`useTimeout`](#usetimeout)
56
+ - [`useLightTimeout`](#uselighttimeout)
43
57
  - [Development](#development)
44
58
  - [Install depenendencies](#install-depenendencies)
45
59
  - [Build the source code](#build-the-source-code)
@@ -88,6 +102,15 @@ export default config
88
102
 
89
103
  ---
90
104
 
105
+ ### What's Changed
106
+
107
+ #### Updates in the latest release 🎉
108
+
109
+ - Add `useDeferCallback`. See [API Reference](#usedefercallback) for more info.
110
+ - Add missing API Referefence sections.
111
+
112
+ ---
113
+
91
114
  ### API Reference
92
115
 
93
116
  #### Browser API
@@ -259,75 +282,26 @@ Applies the same API Reference.
259
282
 
260
283
  ---
261
284
 
262
- ##### `useMediaQuery`
263
-
264
- Get Document Media matches and listen for changes.
265
-
266
- <details>
267
-
268
- <summary style="cursor:pointer">Parameters</summary>
269
-
270
- | Parameter | Type | Default | Description |
271
- |-----------|----------|---------|-------------|
272
- | `query` | `string` | - | A string specifying the media query to parse into a `MediaQueryList`. |
273
- | `options` | `UseMediaQueryOptions\|UseMediaQueryStateOptions` | - | An object defining custom options. |
274
- | `options.updateState` | `boolean` | `true` | Indicates whether the hook will dispatch a React state update when the given `query` change event get dispatched. |
275
- | `options.onChange` | `OnChangeHandler` | - | A custom callback that will be invoked on initial page load and when the given `query` change event get dispatched. |
276
- | | | | This callback is required if `updateState` is set to `false`. |
277
-
278
- </details>
285
+ ##### `useConnection`
279
286
 
280
- ---
287
+ Get states about Internet Connection.
281
288
 
282
289
  <details>
283
290
 
284
291
  <summary style="cursor:pointer">Returns</summary>
285
292
 
286
- Type: `boolean|void`
287
-
288
- - `true` or `false` if the document currently matches the media query list or not.
289
- - `void` if `updateState` is set to `false`.
290
-
291
- </details>
292
-
293
- ---
294
-
295
- <details>
296
-
297
- <summary style="cursor:pointer">Usage</summary>
298
-
299
- ###### Check if user device prefers dark color scheme
300
-
301
- ```tsx
302
- import { useMediaQuery } from '@alessiofrittoli/react-hooks'
303
-
304
- const isDarkOS = useMediaQuery( '(prefers-color-scheme: dark)' )
305
- ```
306
-
307
- ---
293
+ Type: `UseConnectionReturnType`
308
294
 
309
- ###### Listen changes with no state updates
310
-
311
- ```tsx
312
- import { useMediaQuery } from '@alessiofrittoli/react-hooks'
295
+ An object with the following properties:
313
296
 
314
- useMediaQuery( '(prefers-color-scheme: dark)', {
315
- updateState: false,
316
- onChange( matches ) {
317
- console.log( 'is dark OS?', matches )
318
- }
319
- } )
320
- ```
297
+ - `connection`: `online | offline` - Indicates the connections status.
298
+ - `isOnline`: `boolean` - Indicates whether the current device is online.
299
+ - `isOffline`: `boolean` - Indicates whether the current device is offline.
321
300
 
322
301
  </details>
323
302
 
324
303
  ---
325
304
 
326
- ##### `useConnection`
327
-
328
- Docs coming soon
329
-
330
- ---
331
305
 
332
306
  ##### `useDarkMode`
333
307
 
@@ -482,20 +456,26 @@ Just make sure to define both `light` and `dark` theme-color tags in your docume
482
456
 
483
457
  ---
484
458
 
485
- ##### `useIsPortrait`
486
-
487
- Check if device is portrait oriented.
459
+ ##### `useEventListener`
488
460
 
489
- React State get updated when device orientation changes.
461
+ Attach a new Event listener to the `Window`, `Document`, `MediaQueryList` or an `HTMLElement`.
490
462
 
491
463
  <details>
492
464
 
493
- <summary style="cursor:pointer">Returns</summary>
465
+ <summary style="cursor:pointer">Parameters</summary>
494
466
 
495
- Type: `boolean`
467
+ <details>
496
468
 
497
- - `true` if the device is portrait oriented.
498
- - `false` otherwise.
469
+ <summary style="cursor:pointer">Window events</summary>
470
+
471
+ | Parameter | Type | Description |
472
+ |-----------|----------|-------------|
473
+ | `type` | `K\|K[]` | The `Window` event name or an array of event names. |
474
+ | `options` | `WindowListenerOptions<K>` | An object defining init options. |
475
+ | `options.listener` | `WindowEventListener<K>` | The Window Event listener. |
476
+ | `options.onLoad` | `() => void` | A custom callback executed before event listener get attached. |
477
+ | `options.onCleanUp` | `() => void` | A custom callback executed after event listener get removed. |
478
+ | `options.options` | `ListenerOptions` | Specifies characteristics about the event listener. See [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#options). |
499
479
 
500
480
  </details>
501
481
 
@@ -503,33 +483,53 @@ Type: `boolean`
503
483
 
504
484
  <details>
505
485
 
506
- <summary style="cursor:pointer">Usage</summary>
507
-
508
- ###### Check if user device is in landscape
509
-
510
- ```tsx
511
- import { useIsPortrait } from '@alessiofrittoli/react-hooks'
486
+ <summary style="cursor:pointer">Document events</summary>
512
487
 
513
- const isLandscape = ! useIsPortrait()
514
- ```
488
+ | Parameter | Type | Description |
489
+ |-----------|----------|-------------|
490
+ | `type` | `K\|K[]` | The `Document` event name or an array of event names. |
491
+ | `options` | `DocumentListenerOptions<K>` | An object defining init options. |
492
+ | `options.target` | `Document\|null\|React.RefObject<Document\|null>` | The `Document` reference or a React RefObject of the `Document`. |
493
+ | `options.listener` | `DocumentEventListener<K>` | The Document Event listener. |
494
+ | `options.onLoad` | `() => void` | A custom callback executed before event listener get attached. |
495
+ | `options.onCleanUp` | `() => void` | A custom callback executed after event listener get removed. |
496
+ | `options.options` | `ListenerOptions` | Specifies characteristics about the event listener. See [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#options). |
515
497
 
516
498
  </details>
517
499
 
518
500
  ---
519
501
 
520
- #### DOM API
502
+ <details>
521
503
 
522
- ##### `useScrollBlock`
504
+ <summary style="cursor:pointer">HTMLElement events</summary>
523
505
 
524
- Prevent Element overflow.
506
+ | Parameter | Type | Description |
507
+ |-----------|----------|-------------|
508
+ | `type` | `K\|K[]` | The `HTMLElement` event name or an array of event names. |
509
+ | `options` | `ElementListenerOptions<K>` | An object defining init options. |
510
+ | `options.target` | `T\|React.RefObject<T\| null>` | The React RefObject of the target where the listener get attached to. |
511
+ | `options.listener` | `ElementEventListener<K>` | The HTMLElement Event listener. |
512
+ | `options.onLoad` | `() => void` | A custom callback executed before event listener get attached. |
513
+ | `options.onCleanUp` | `() => void` | A custom callback executed after event listener get removed. |
514
+ | `options.options` | `ListenerOptions` | Specifies characteristics about the event listener. See [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#options). |
515
+
516
+ </details>
517
+
518
+ ---
525
519
 
526
520
  <details>
527
521
 
528
- <summary style="cursor:pointer">Parameters</summary>
522
+ <summary style="cursor:pointer">MediaQuery events</summary>
529
523
 
530
- | Parameter | Type | Default | Description |
531
- |-----------|------|---------|-------------|
532
- | `target` | `React.RefObject<HTMLElement\|null>` | `Document.documentElement` | (Optional) The React RefObject target HTMLElement. |
524
+ | Parameter | Type | Description |
525
+ |-----------|----------|-------------|
526
+ | `type` | `change` | The `MediaQueryList` event name. |
527
+ | `options` | `MediaQueryListenerOptions` | An object defining init options. |
528
+ | `options.query` | `string` | The Media Query string to check. |
529
+ | `options.listener` | `MediaQueryChangeListener` | The MediaQueryList Event listener. |
530
+ | `options.onLoad` | `() => void` | A custom callback executed before event listener get attached. |
531
+ | `options.onCleanUp` | `() => void` | A custom callback executed after event listener get removed. |
532
+ | `options.options` | `ListenerOptions` | Specifies characteristics about the event listener. See [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#options). |
533
533
 
534
534
  </details>
535
535
 
@@ -537,11 +537,19 @@ Prevent Element overflow.
537
537
 
538
538
  <details>
539
539
 
540
- <summary style="cursor:pointer">Returns</summary>
540
+ <summary style="cursor:pointer">Custom events</summary>
541
541
 
542
- Type: `[ () => void, () => void ]`
542
+ | Parameter | Type | Description |
543
+ |-----------|----------|-------------|
544
+ | `type` | `K\|K[]` | The custom event name or an array of event names. |
545
+ | `options` | `CustomEventListenerOptions<T, K>` | An object defining init options. |
546
+ | `options.target` | `Document\|HTMLElement\|null\|React.RefObject<Document\|HTMLElement\|null>` | (Optional) The target where the listener get attached to. If not set, the listener will get attached to the `Window` object. |
547
+ | `options.listener` | `( event: T[ K ] ) => void` | The Event listener. |
548
+ | `options.onLoad` | `() => void` | A custom callback executed before event listener get attached. |
549
+ | `options.onCleanUp` | `() => void` | A custom callback executed after event listener get removed. |
550
+ | `options.options` | `ListenerOptions` | Specifies characteristics about the event listener. See [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#options). |
543
551
 
544
- A tuple with block and restore scroll callbacks.
552
+ </details>
545
553
 
546
554
  </details>
547
555
 
@@ -551,154 +559,354 @@ A tuple with block and restore scroll callbacks.
551
559
 
552
560
  <summary style="cursor:pointer">Usage</summary>
553
561
 
554
- ###### Block Document Overflow
562
+ ###### Attach listeners to the Window object
555
563
 
556
564
  ```tsx
557
- import { useScrollBlock } from '@alessiofrittoli/react-hooks'
565
+ 'use client'
558
566
 
559
- const [ blockScroll, restoreScroll ] = useScrollBlock()
567
+ import { useCallback } from 'react'
568
+ import { useEventListener } from '@alessiofrittoli/react-hooks'
560
569
 
561
- const openPopUpHandler = useCallback( () => {
562
- ...
563
- blockScroll()
564
- }, [ blockScroll ] )
570
+ export const MyComponent: React.FC = () => {
565
571
 
566
- const closePopUpHandler = useCallback( () => {
567
- ...
568
- restoreScroll()
569
- }, [ restoreScroll ] )
572
+ useEventListener( 'popstate', {
573
+ listener: useCallback( event => {
574
+ ...
575
+ }, [] ),
576
+ } )
570
577
 
571
- ...
578
+ }
572
579
  ```
573
580
 
574
581
  ---
575
582
 
576
- ###### Block HTML Element Overflow
583
+ ###### Attach listeners to the Document object
577
584
 
578
585
  ```tsx
579
- const elementRef = useRef<HTMLDivElement>( null )
586
+ 'use client'
580
587
 
581
- const [ blockScroll, restoreScroll ] = useScrollBlock( elementRef )
588
+ import { useCallback } from 'react'
589
+ import { useEventListener } from '@alessiofrittoli/react-hooks'
582
590
 
583
- const scrollBlockHandler = useCallback( () => {
584
- ...
585
- blockScroll()
586
- }, [ blockScroll ] )
591
+ export const MyComponent: React.FC = () => {
587
592
 
588
- const scrollRestoreHandler = useCallback( () => {
589
- ...
590
- restoreScroll()
591
- }, [ restoreScroll ] )
593
+ useEventListener( 'click', {
594
+ target : typeof document !== 'undefined' ? document : null,
595
+ listener : useCallback( event => {
596
+ ...
597
+ }, [] ),
598
+ } )
592
599
 
593
- ...
600
+ }
594
601
  ```
595
602
 
596
- </details>
597
-
598
603
  ---
599
604
 
600
- ##### `useFocusTrap`
605
+ ###### Attach listeners to an HTMLElement
601
606
 
602
- Trap focus inside the given HTML Element.
607
+ ```tsx
608
+ 'use client'
603
609
 
604
- This comes pretty handy when rendering a modal that shouldn't be closed without a user required action.
610
+ import { useCallback, useRef } from 'react'
611
+ import { useEventListener } from '@alessiofrittoli/react-hooks'
605
612
 
606
- <details>
613
+ export const MyComponent: React.FC = () => {
607
614
 
608
- <summary style="cursor:pointer">Parameters</summary>
615
+ const buttonRef = useRef<HTMLButtonElement>( null )
609
616
 
610
- | Parameter | Type | Description |
611
- |-----------|------|-------------|
612
- | `target` | `React.RefObject<HTMLElement\|null>` | The target HTMLElement React RefObject to trap focus within. |
613
- | | | If no target is given, you must provide the target HTMLElement when calling `setFocusTrap`. |
617
+ useEventListener( 'click', {
618
+ target: buttonRef,
619
+ listener: useCallback( event => {
620
+ ...
621
+ }, [] ),
622
+ } )
614
623
 
615
- </details>
624
+ return (
625
+ <button ref={ buttonRef }>Button</button>
626
+ )
627
+
628
+ }
629
+ ```
616
630
 
617
631
  ---
618
632
 
619
- <details>
633
+ ###### Attach listeners to a MediaQueryList
620
634
 
621
- <summary style="cursor:pointer">Returns</summary>
635
+ ```tsx
636
+ import { useCallback } from 'react'
637
+ import { useEventListener } from '@alessiofrittoli/react-hooks'
622
638
 
623
- Type: `readonly [ SetFocusTrap, RestoreFocusTrap ]`
639
+ export const MyComponent: React.FC = () => {
624
640
 
625
- A tuple containing:
641
+ useEventListener( 'change', {
642
+ query : '(max-width: 768px)',
643
+ listener : useCallback( event => {
644
+ if ( event.matches ) {
645
+ ...
646
+ }
647
+ }, [] )
648
+ } )
626
649
 
627
- - `setFocusTrap`: A function to enable the focus trap. Optionally accept an HTMLElement as target.
628
- - `restoreFocusTrap`: A function to restore the previous focus state.
650
+ }
651
+ ```
652
+
653
+ ---
654
+
655
+ ###### Listen dispatched custom events
656
+
657
+ ```tsx
658
+ import { useCallback } from 'react'
659
+ import { useEventListener } from '@alessiofrittoli/react-hooks'
660
+
661
+ class CustomEvent extends Event
662
+ {
663
+ isCustom: boolean
664
+
665
+ constructor( type: string, eventInitDict?: EventInit )
666
+ {
667
+ super( type, eventInitDict )
668
+ this.isCustom = true
669
+ }
670
+ }
671
+
672
+
673
+ type CustomEventMap = {
674
+ customEventName: CustomEvent
675
+ }
676
+
677
+
678
+ export const MyComponent: React.FC = () => {
679
+
680
+ const clickHandler = useCallback( () => {
681
+ document.dispatchEvent( new CustomEvent( 'customEventName' ) )
682
+ }, [] )
683
+
684
+ useEventListener<CustomEventMap>( 'customEventName', {
685
+ target : typeof document !== 'undefined' ? document : null,
686
+ listener : useCallback( event => {
687
+ if ( event.isCustom ) {
688
+ ...
689
+ }
690
+ }, [] )
691
+ } )
692
+
693
+
694
+ return (
695
+ <button onClick={ clickHandler }>Click me to dispatch custom event</button>
696
+ )
697
+
698
+ }
699
+ ```
700
+
701
+ ---
629
702
 
630
703
  </details>
631
704
 
632
705
  ---
633
706
 
634
- <details>
707
+ ##### `useIsPortrait`
635
708
 
636
- <summary style="cursor:pointer">Usage</summary>
709
+ Check if device is portrait oriented.
637
710
 
638
- ###### Defining the target on hook initialization
711
+ React State get updated when device orientation changes.
639
712
 
640
- ```tsx
641
- import { useFocusTrap } from '@alessiofrittoli/react-hooks'
713
+ <details>
642
714
 
643
- const modalRef = useRef<HTMLDivElement>( null )
644
- const [ setFocusTrap, restoreFocusTrap ] = useFocusTrap( modalRef )
715
+ <summary style="cursor:pointer">Returns</summary>
645
716
 
646
- const modalOpenHandler = useCallback( () => {
647
- if ( ! modalRef.current ) return
648
- // ... open modal
649
- setFocusTrap()
650
- modalRef.current.focus() // focus the dialog so next tab will focus the next element inside the modal
651
- }, [ setFocusTrap ] )
717
+ Type: `boolean`
652
718
 
653
- const modalCloseHandler = useCallback( () => {
654
- // ... close modal
655
- restoreFocusTrap() // cancel focus trap and restore focus to the last active element before enablig the focus trap
656
- }, [ restoreFocusTrap ] )
657
- ```
719
+ - `true` if the device is portrait oriented.
720
+ - `false` otherwise.
721
+
722
+ </details>
658
723
 
659
724
  ---
660
725
 
661
- ###### Defining the target ondemand
726
+ <details>
662
727
 
663
- ```tsx
664
- import { useFocusTrap } from '@alessiofrittoli/react-hooks'
728
+ <summary style="cursor:pointer">Usage</summary>
665
729
 
666
- const modalRef = useRef<HTMLDivElement>( null )
667
- const modal2Ref = useRef<HTMLDivElement>( null )
668
- const [ setFocusTrap, restoreFocusTrap ] = useFocusTrap()
730
+ ###### Check if user device is in landscape
669
731
 
670
- const modalOpenHandler = useCallback( () => {
671
- if ( ! modalRef.current ) return
672
- // ... open modal
673
- setFocusTrap( modalRef.current )
674
- modalRef.current.focus()
675
- }, [ setFocusTrap ] )
732
+ ```tsx
733
+ import { useIsPortrait } from '@alessiofrittoli/react-hooks'
676
734
 
677
- const modal2OpenHandler = useCallback( () => {
678
- if ( ! modal2Ref.current ) return
679
- // ... open modal
680
- setFocusTrap( modal2Ref.current )
681
- modal2Ref.current.focus()
682
- }, [ setFocusTrap ] )
735
+ const isLandscape = ! useIsPortrait()
683
736
  ```
684
737
 
685
738
  </details>
686
739
 
687
740
  ---
688
741
 
689
- ##### `useInView`
742
+ ##### `useMediaQuery`
690
743
 
691
- Check if the given target Element is intersecting with an ancestor Element or with a top-level document's viewport.
744
+ Get Document Media matches and listen for changes.
692
745
 
693
746
  <details>
694
747
 
695
748
  <summary style="cursor:pointer">Parameters</summary>
696
749
 
697
- | Parameter | Type | Description |
698
- |-----------|------|-------------|
699
- | `target` | `React.RefObject<Element\|null>` | The React.RefObject of the target Element to observe. |
700
- | `options` | `UseInViewOptions` | (Optional) An object defining custom `IntersectionObserver` options. |
701
- | `options.root` | `Element\|Document\|false\|null` | (Optional) Identifies the `Element` or `Document` whose bounds are treated as the bounding box of the viewport for the Element which is the observer's target. |
750
+ | Parameter | Type | Default | Description |
751
+ |-----------|----------|---------|-------------|
752
+ | `query` | `string` | - | A string specifying the media query to parse into a `MediaQueryList`. |
753
+ | `options` | `UseMediaQueryOptions\|UseMediaQueryStateOptions` | - | An object defining custom options. |
754
+ | `options.updateState` | `boolean` | `true` | Indicates whether the hook will dispatch a React state update when the given `query` change event get dispatched. |
755
+ | `options.onChange` | `OnChangeHandler` | - | A custom callback that will be invoked on initial page load and when the given `query` change event get dispatched. |
756
+ | | | | This callback is required if `updateState` is set to `false`. |
757
+
758
+ </details>
759
+
760
+ ---
761
+
762
+ <details>
763
+
764
+ <summary style="cursor:pointer">Returns</summary>
765
+
766
+ Type: `boolean|void`
767
+
768
+ - `true` or `false` if the document currently matches the media query list or not.
769
+ - `void` if `updateState` is set to `false`.
770
+
771
+ </details>
772
+
773
+ ---
774
+
775
+ <details>
776
+
777
+ <summary style="cursor:pointer">Usage</summary>
778
+
779
+ ###### Check if user device prefers dark color scheme
780
+
781
+ ```tsx
782
+ import { useMediaQuery } from '@alessiofrittoli/react-hooks'
783
+
784
+ const isDarkOS = useMediaQuery( '(prefers-color-scheme: dark)' )
785
+ ```
786
+
787
+ ---
788
+
789
+ ###### Listen changes with no state updates
790
+
791
+ ```tsx
792
+ import { useMediaQuery } from '@alessiofrittoli/react-hooks'
793
+
794
+ useMediaQuery( '(prefers-color-scheme: dark)', {
795
+ updateState: false,
796
+ onChange( matches ) {
797
+ console.log( 'is dark OS?', matches )
798
+ }
799
+ } )
800
+ ```
801
+
802
+ </details>
803
+
804
+ ---
805
+
806
+ #### DOM API
807
+
808
+ ##### `useFocusTrap`
809
+
810
+ Trap focus inside the given HTML Element.
811
+
812
+ This comes pretty handy when rendering a modal that shouldn't be closed without a user required action.
813
+
814
+ <details>
815
+
816
+ <summary style="cursor:pointer">Parameters</summary>
817
+
818
+ | Parameter | Type | Description |
819
+ |-----------|------|-------------|
820
+ | `target` | `React.RefObject<HTMLElement\|null>` | The target HTMLElement React RefObject to trap focus within. |
821
+ | | | If no target is given, you must provide the target HTMLElement when calling `setFocusTrap`. |
822
+
823
+ </details>
824
+
825
+ ---
826
+
827
+ <details>
828
+
829
+ <summary style="cursor:pointer">Returns</summary>
830
+
831
+ Type: `readonly [ SetFocusTrap, RestoreFocusTrap ]`
832
+
833
+ A tuple containing:
834
+
835
+ - `setFocusTrap`: A function to enable the focus trap. Optionally accept an HTMLElement as target.
836
+ - `restoreFocusTrap`: A function to restore the previous focus state.
837
+
838
+ </details>
839
+
840
+ ---
841
+
842
+ <details>
843
+
844
+ <summary style="cursor:pointer">Usage</summary>
845
+
846
+ ###### Defining the target on hook initialization
847
+
848
+ ```tsx
849
+ import { useFocusTrap } from '@alessiofrittoli/react-hooks'
850
+
851
+ const modalRef = useRef<HTMLDivElement>( null )
852
+ const [ setFocusTrap, restoreFocusTrap ] = useFocusTrap( modalRef )
853
+
854
+ const modalOpenHandler = useCallback( () => {
855
+ if ( ! modalRef.current ) return
856
+ // ... open modal
857
+ setFocusTrap()
858
+ modalRef.current.focus() // focus the dialog so next tab will focus the next element inside the modal
859
+ }, [ setFocusTrap ] )
860
+
861
+ const modalCloseHandler = useCallback( () => {
862
+ // ... close modal
863
+ restoreFocusTrap() // cancel focus trap and restore focus to the last active element before enablig the focus trap
864
+ }, [ restoreFocusTrap ] )
865
+ ```
866
+
867
+ ---
868
+
869
+ ###### Defining the target ondemand
870
+
871
+ ```tsx
872
+ import { useFocusTrap } from '@alessiofrittoli/react-hooks'
873
+
874
+ const modalRef = useRef<HTMLDivElement>( null )
875
+ const modal2Ref = useRef<HTMLDivElement>( null )
876
+ const [ setFocusTrap, restoreFocusTrap ] = useFocusTrap()
877
+
878
+ const modalOpenHandler = useCallback( () => {
879
+ if ( ! modalRef.current ) return
880
+ // ... open modal
881
+ setFocusTrap( modalRef.current )
882
+ modalRef.current.focus()
883
+ }, [ setFocusTrap ] )
884
+
885
+ const modal2OpenHandler = useCallback( () => {
886
+ if ( ! modal2Ref.current ) return
887
+ // ... open modal
888
+ setFocusTrap( modal2Ref.current )
889
+ modal2Ref.current.focus()
890
+ }, [ setFocusTrap ] )
891
+ ```
892
+
893
+ </details>
894
+
895
+ ---
896
+
897
+ ##### `useInView`
898
+
899
+ Check if the given target Element is intersecting with an ancestor Element or with a top-level document's viewport.
900
+
901
+ <details>
902
+
903
+ <summary style="cursor:pointer">Parameters</summary>
904
+
905
+ | Parameter | Type | Description |
906
+ |-----------|------|-------------|
907
+ | `target` | `React.RefObject<Element\|null>` | The React.RefObject of the target Element to observe. |
908
+ | `options` | `UseInViewOptions` | (Optional) An object defining custom `IntersectionObserver` options. |
909
+ | `options.root` | `Element\|Document\|false\|null` | (Optional) Identifies the `Element` or `Document` whose bounds are treated as the bounding box of the viewport for the Element which is the observer's target. |
702
910
  | `options.margin` | `MarginType` | (Optional) A string, formatted similarly to the CSS margin property's value, which contains offsets for one or more sides of the root's bounding box. |
703
911
  | `options.amount` | `'all'\|'some'\|number\|number[]` | (Optional) The intersecting target thresholds. |
704
912
  | | | Threshold can be set to: |
@@ -946,36 +1154,29 @@ const AsyncStartExample: React.FC = () => {
946
1154
 
947
1155
  ---
948
1156
 
949
- #### Miscellaneous
950
-
951
- ---
1157
+ ##### `useScrollBlock`
952
1158
 
953
- ##### `useInput`
1159
+ Prevent Element overflow.
954
1160
 
955
- Docs coming soon
1161
+ <details>
956
1162
 
957
- ---
1163
+ <summary style="cursor:pointer">Parameters</summary>
958
1164
 
959
- ##### `useEffectOnce`
1165
+ | Parameter | Type | Default | Description |
1166
+ |-----------|------|---------|-------------|
1167
+ | `target` | `React.RefObject<HTMLElement\|null>` | `Document.documentElement` | (Optional) The React RefObject target HTMLElement. |
960
1168
 
961
- Docs coming soon
1169
+ </details>
962
1170
 
963
1171
  ---
964
1172
 
965
- ##### `useIsClient`
966
-
967
- Check if the React Hook or Component where this hook is executed is running in a browser environment.
968
-
969
- This is pretty usefull to avoid hydration errors.
970
-
971
1173
  <details>
972
1174
 
973
1175
  <summary style="cursor:pointer">Returns</summary>
974
1176
 
975
- Type: `boolean`
1177
+ Type: `[ () => void, () => void ]`
976
1178
 
977
- - `true` if the React Hook or Component is running in a browser environment.
978
- - `false` otherwise.
1179
+ A tuple with block and restore scroll callbacks.
979
1180
 
980
1181
  </details>
981
1182
 
@@ -985,42 +1186,109 @@ Type: `boolean`
985
1186
 
986
1187
  <summary style="cursor:pointer">Usage</summary>
987
1188
 
988
- ###### Basic usage
1189
+ ###### Block Document Overflow
989
1190
 
990
1191
  ```tsx
991
- 'use client'
1192
+ import { useScrollBlock } from '@alessiofrittoli/react-hooks'
992
1193
 
993
- import { useIsClient } from '@alessiofrittoli/react-hooks'
1194
+ const [ blockScroll, restoreScroll ] = useScrollBlock()
994
1195
 
995
- export const ClientComponent: React.FC = () => {
1196
+ const openPopUpHandler = useCallback( () => {
1197
+ ...
1198
+ blockScroll()
1199
+ }, [ blockScroll ] )
996
1200
 
997
- const isClient = useIsClient()
1201
+ const closePopUpHandler = useCallback( () => {
1202
+ ...
1203
+ restoreScroll()
1204
+ }, [ restoreScroll ] )
998
1205
 
999
- return (
1000
- <div>Running { ! isClient ? 'server' : 'client' }-side</div>
1001
- )
1206
+ ...
1207
+ ```
1002
1208
 
1003
- }
1209
+ ---
1210
+
1211
+ ###### Block HTML Element Overflow
1212
+
1213
+ ```tsx
1214
+ const elementRef = useRef<HTMLDivElement>( null )
1215
+
1216
+ const [ blockScroll, restoreScroll ] = useScrollBlock( elementRef )
1217
+
1218
+ const scrollBlockHandler = useCallback( () => {
1219
+ ...
1220
+ blockScroll()
1221
+ }, [ blockScroll ] )
1222
+
1223
+ const scrollRestoreHandler = useCallback( () => {
1224
+ ...
1225
+ restoreScroll()
1226
+ }, [ restoreScroll ] )
1227
+
1228
+ ...
1004
1229
  ```
1005
1230
 
1006
1231
  </details>
1007
1232
 
1008
1233
  ---
1009
1234
 
1010
- ##### `useIsFirstRender`
1235
+ #### Miscellaneous
1011
1236
 
1012
- Check if is first React Hook/Component render.
1237
+ ##### `useInput`
1238
+
1239
+ Handle input states with ease.
1240
+
1241
+ <details>
1242
+
1243
+ <summary style="cursor:pointer">Type Parameters</summary>
1244
+
1245
+ | Parameter | Description |
1246
+ |-----------|------------------------|
1247
+ | `I` | The input value type. |
1248
+ | `O` | The output value type. |
1249
+
1250
+ </details>
1251
+
1252
+ ---
1253
+
1254
+ <details>
1255
+
1256
+ <summary style="cursor:pointer">Parameters</summary>
1257
+
1258
+ | Parameter | Type | Default | Description |
1259
+ |-----------|----------|---------|-----------------------------|
1260
+ | `options` | `UseInputOptions<I, O>` | `{}` | An object defining custom options. |
1261
+ | `options.inputRef` | `React.RefObject<InputType>` | - | (Optional) The React HTML input element ref. |
1262
+ | `options.initialValue` | `O\|null` | - | (Optional) The input initial value. |
1263
+ | `options.touchTimeout` | `number` | 600 | (Optional) A timeout in milliseconds which will be used to define the input as "touched" thus validations are triggered and errors can be displayed. |
1264
+ | `options.validate` | `ValidateValueHandler<O>` | - | (Optional) Value validation handler. If `parse` callback is given, the `value` will be parsed before validation. |
1265
+ | `options.parse` | `ParseValueHandler<I, O>` | - | (Optional) Parse value. |
1266
+ | `options.onChange` | `ChangeHandler<O>` | - | (Optional) A callable function executed when the `ChangeEvent` is dispatched on the HTML input element. |
1267
+
1268
+ </details>
1269
+
1270
+ ---
1013
1271
 
1014
1272
  <details>
1015
1273
 
1016
1274
  <summary style="cursor:pointer">Returns</summary>
1017
1275
 
1018
- Type: `boolean`
1276
+ Type: `UseInputOutput<I, O>`
1019
1277
 
1020
- - `true` at the mount time.
1021
- - `false` otherwise.
1278
+ An object containing the following properties:
1022
1279
 
1023
- Note that if the React Hook/Component has no state updates, `useIsFirstRender` will always return `true`.
1280
+ | Property | Type | Description |
1281
+ |-----------------|------|-------------|
1282
+ | `isEmpty` | `boolean` | Indicates whether the Input is empty or not. |
1283
+ | `hasError` | `boolean` | Indicates whether the input has error or not. |
1284
+ | | | It will return true if the Input does not pass the validation checks and it has been touched. |
1285
+ | | | Please refer to the `isValid` property to check the Input validity regardless of whether it has been touched or not. |
1286
+ | `changeHandler` | `React.ChangeEventHandler<InputType>` | Change handler callback used to handle Input change events. |
1287
+ | `blurHandler` | `() => void` | Blur handler callback used to handle Input blur events. |
1288
+ | `setValue` | `( value: O ) => void` | Call `setValue` method to update input value. |
1289
+ | `submit` | `() => void` | Call `submit` method to re-run validations and ensure error state is updated successfully. |
1290
+ | `reset` | `() => void` | Call `reset` method to reset the Input state. |
1291
+ | `focus` | `() => void` | Call `focus` method to focus the Input Element. `inputRef` must be provided in the input options. |
1024
1292
 
1025
1293
  </details>
1026
1294
 
@@ -1033,28 +1301,216 @@ Note that if the React Hook/Component has no state updates, `useIsFirstRender` w
1033
1301
  ###### Basic usage
1034
1302
 
1035
1303
  ```tsx
1036
- 'use client'
1304
+ const MyComponent: React.FC = () => {
1037
1305
 
1038
- import { useIsFirstRender } from '@alessiofrittoli/react-hooks'
1306
+ const input = useInput<string>()
1039
1307
 
1040
- export const ClientComponent: React.FC = () => {
1308
+ return (
1309
+ <input
1310
+ type='text'
1311
+ value={ input.value || '' }
1312
+ onChange={ input.changeHandler }
1313
+ onBlur={ input.blurHandler }
1314
+ />
1315
+ )
1041
1316
 
1042
- const isFirstRender = useIsFirstRender()
1043
- const [ counter, setCounter ] = useState( 0 )
1317
+ }
1318
+ ```
1319
+
1320
+ ---
1321
+
1322
+ ###### Displaying custom error messages
1323
+
1324
+ ```tsx
1325
+ import { useInput, type ValidateValueHandler } from '@alessiofrittoli/react-hooks'
1326
+
1327
+ const isNotEmpty: ValidateValueHandler<string> = value => (
1328
+ ! value ? false : value.trim().length > 0
1329
+ )
1330
+
1331
+ const MyComponent: React.FC = () => {
1332
+
1333
+ const input = useInput<string>( {
1334
+ validate: isNotEmpty,
1335
+ } )
1336
+
1337
+ return (
1338
+ <>
1339
+ <input
1340
+ value={ input.value || '' }
1341
+ onChange={ input.changeHandler }
1342
+ onBlur={ input.blurHandler }
1343
+ />
1344
+ { input.hasError && (
1345
+ <span>The input cannot be empty.</span>
1346
+ ) }
1347
+ </>
1348
+ )
1349
+
1350
+ }
1351
+ ```
1352
+
1353
+ ---
1354
+
1355
+ ###### Parsing and validating parsed value
1356
+
1357
+ ```tsx
1358
+ import { formatDate, isValidDate } from '@alessiofrittoli/date-utils'
1359
+ import { useInput, type ValidateValueHandler, type ParseValueHandler } from '@alessiofrittoli/react-hooks'
1360
+
1361
+ const parseStringToDate: ParseValueHandler<string, Date> = value => (
1362
+ value ? new Date( value ) : undefined
1363
+ )
1364
+
1365
+
1366
+ const validateInputDate: ValidateValueHandler<Date> = value => (
1367
+ isValidDate( value ) && value.getTime() > Date.now()
1368
+ )
1369
+
1370
+
1371
+ const MyComponent: React.FC = () => {
1372
+
1373
+ const input = useInput<string, Date>( {
1374
+ parse : parseStringToDate,
1375
+ validate : validateInputDate,
1376
+ } )
1377
+
1378
+
1379
+ return (
1380
+ <>
1381
+ <input
1382
+ type='datetime-local'
1383
+ value={ input.value ? formatDate( input.value, 'Y-m-dTH:i' ) : '' }
1384
+ onChange={ input.changeHandler }
1385
+ onBlur={ input.blurHandler }
1386
+ />
1387
+ { input.hasError && (
1388
+ <span>Please choose a date no earlier than today</span>
1389
+ ) }
1390
+ </>
1391
+ )
1392
+
1393
+ }
1394
+ ```
1395
+
1396
+ </details>
1397
+
1398
+ ---
1399
+
1400
+ ##### `useDeferCallback`
1401
+
1402
+ `useDeferCallback` will return a memoized and deferred version of the callback that only changes if one of the `inputs` in the dependency list has changed.
1403
+
1404
+ Since [`deferCallback`](https://npmjs.com/package/@alessiofrittoli/web-utils?activeTab=readme#deferCallback) returns a new function when called, it may cause your child components to uselessly re-validate when a state update occurs in the main component.
1405
+ To avoid these pitfalls you can memoize and defer your task with `useDeferCallback`.
1406
+
1407
+ Take a look at [`deferTask`](https://npmjs.com/package/@alessiofrittoli/web-utils?activeTab=readme#deferTask) to defer single tasks in a function handler.
1408
+
1409
+ <details>
1410
+
1411
+ <summary style="cursor:pointer">Type Parameters</summary>
1412
+
1413
+ | Parameter | Description |
1414
+ |-----------|------------------------------|
1415
+ | `T` | The task function definition. `unknown` types will be inherited by your function type definition. |
1416
+ | `U` | The task function arguments. `unknown` types will be inherited by your function type. |
1417
+
1418
+ </details>
1419
+
1420
+ ---
1421
+
1422
+ <details>
1423
+
1424
+ <summary style="cursor:pointer">Parameters</summary>
1425
+
1426
+ | Parameter | Type | Description |
1427
+ |-----------|----------|-----------------------------|
1428
+ | `task` | `T` | The task callable function. |
1429
+
1430
+ </details>
1431
+
1432
+ ---
1433
+
1434
+ <details>
1435
+
1436
+ <summary style="cursor:pointer">Returns</summary>
1437
+
1438
+ Type: `( ...args: U ) => Promise<Awaited<ReturnType<T>>>`
1439
+
1440
+ A new memoized handler which returns a new Promise that returns the `task` result once fulfilled.
1441
+
1442
+ </details>
1443
+
1444
+ ---
1445
+
1446
+ <details>
1447
+
1448
+ <summary style="cursor:pointer">Usage</summary>
1449
+
1450
+ ```tsx
1451
+ const MyComponent: React.FC = () => {
1452
+
1453
+ const clickHandler = useDeferCallback<React.MouseEventHandler>(
1454
+ event => { ... }, []
1455
+ )
1456
+
1457
+ return (
1458
+ <button onClick={ clickHandler }>Button</button>
1459
+ )
1460
+
1461
+ }
1462
+ ```
1463
+
1464
+ </details>
1465
+
1466
+ ---
1467
+
1468
+ ##### `useEffectOnce`
1469
+
1470
+ Modified version of `useEffect` that only run once on intial load.
1471
+
1472
+ <details>
1473
+
1474
+ <summary style="cursor:pointer">Parameters</summary>
1475
+
1476
+ | Parameter | Type | Description |
1477
+ |-----------|------------------------|-------------|
1478
+ | `effect` | `React.EffectCallback` | Imperative function that can return a cleanup function. |
1479
+
1480
+ </details>
1481
+
1482
+ ---
1483
+
1484
+ <details>
1485
+
1486
+ <summary style="cursor:pointer">Usage</summary>
1487
+
1488
+ ```tsx
1489
+ 'use client'
1490
+
1491
+ import { useEffect, useState } from 'react'
1492
+ import { useEffectOnce } from '@alessiofrittoli/react-hooks'
1493
+
1494
+ export const ClientComponent: React.FC = () => {
1495
+
1496
+ const [ count, setCount ] = useState( 0 )
1044
1497
 
1045
1498
  useEffect( () => {
1046
1499
  const intv = setInterval( () => {
1047
- setCounter( prev => prev + 1 )
1500
+ setCount( prev => prev + 1 ) // update state each 1s
1048
1501
  }, 1000 )
1049
1502
  return () => clearInterval( intv )
1050
1503
  }, [] )
1051
1504
 
1505
+ useEffectOnce( () => {
1506
+ console.log( 'Component did mount' )
1507
+ return () => {
1508
+ console.log( 'Component did unmount' )
1509
+ }
1510
+ } )
1511
+
1052
1512
  return (
1053
- <div>
1054
- { isFirstRender ? 'First render' : 'Subsequent render' }
1055
- <hr />
1056
- { counter }
1057
- </div>
1513
+ <div>{ count }</div>
1058
1514
  )
1059
1515
 
1060
1516
  }
@@ -1085,8 +1541,6 @@ Modified version of `useEffect` that skips the first render.
1085
1541
 
1086
1542
  <summary style="cursor:pointer">Usage</summary>
1087
1543
 
1088
- ###### Basic usage
1089
-
1090
1544
  ```tsx
1091
1545
  'use client'
1092
1546
 
@@ -1129,6 +1583,104 @@ export const ClientComponent: React.FC = () => {
1129
1583
 
1130
1584
  ---
1131
1585
 
1586
+ ##### `useIsClient`
1587
+
1588
+ Check if the React Hook or Component where this hook is executed is running in a browser environment.
1589
+
1590
+ This is pretty usefull to avoid hydration errors.
1591
+
1592
+ <details>
1593
+
1594
+ <summary style="cursor:pointer">Returns</summary>
1595
+
1596
+ Type: `boolean`
1597
+
1598
+ - `true` if the React Hook or Component is running in a browser environment.
1599
+ - `false` otherwise.
1600
+
1601
+ </details>
1602
+
1603
+ ---
1604
+
1605
+ <details>
1606
+
1607
+ <summary style="cursor:pointer">Usage</summary>
1608
+
1609
+ ```tsx
1610
+ 'use client'
1611
+
1612
+ import { useIsClient } from '@alessiofrittoli/react-hooks'
1613
+
1614
+ export const ClientComponent: React.FC = () => {
1615
+
1616
+ const isClient = useIsClient()
1617
+
1618
+ return (
1619
+ <div>Running { ! isClient ? 'server' : 'client' }-side</div>
1620
+ )
1621
+
1622
+ }
1623
+ ```
1624
+
1625
+ </details>
1626
+
1627
+ ---
1628
+
1629
+ ##### `useIsFirstRender`
1630
+
1631
+ Check if is first React Hook/Component render.
1632
+
1633
+ <details>
1634
+
1635
+ <summary style="cursor:pointer">Returns</summary>
1636
+
1637
+ Type: `boolean`
1638
+
1639
+ - `true` at the mount time.
1640
+ - `false` otherwise.
1641
+
1642
+ Note that if the React Hook/Component has no state updates, `useIsFirstRender` will always return `true`.
1643
+
1644
+ </details>
1645
+
1646
+ ---
1647
+
1648
+ <details>
1649
+
1650
+ <summary style="cursor:pointer">Usage</summary>
1651
+
1652
+ ```tsx
1653
+ 'use client'
1654
+
1655
+ import { useIsFirstRender } from '@alessiofrittoli/react-hooks'
1656
+
1657
+ export const ClientComponent: React.FC = () => {
1658
+
1659
+ const isFirstRender = useIsFirstRender()
1660
+ const [ counter, setCounter ] = useState( 0 )
1661
+
1662
+ useEffect( () => {
1663
+ const intv = setInterval( () => {
1664
+ setCounter( prev => prev + 1 )
1665
+ }, 1000 )
1666
+ return () => clearInterval( intv )
1667
+ }, [] )
1668
+
1669
+ return (
1670
+ <div>
1671
+ { isFirstRender ? 'First render' : 'Subsequent render' }
1672
+ <hr />
1673
+ { counter }
1674
+ </div>
1675
+ )
1676
+
1677
+ }
1678
+ ```
1679
+
1680
+ </details>
1681
+
1682
+ ---
1683
+
1132
1684
  ##### `usePagination`
1133
1685
 
1134
1686
  Get pagination informations based on the given options.
@@ -1141,7 +1693,597 @@ See [`paginate`](https://github.com/alessiofrittoli/math-utils/blob/master/docs/
1141
1693
 
1142
1694
  ##### `useSelection`
1143
1695
 
1144
- Docs coming soon
1696
+ A React hook for managing selection states in an array.
1697
+
1698
+ Provides functionality for single and group selection, as well as resetting the selection.
1699
+
1700
+ <details>
1701
+
1702
+ <summary style="cursor:pointer">Type Parameters</summary>
1703
+
1704
+ | Parameter | Description |
1705
+ |-----------|------------------------------|
1706
+ | `V` | The type of the values in the `array`. |
1707
+
1708
+ </details>
1709
+
1710
+ ---
1711
+
1712
+ <details>
1713
+
1714
+ <summary style="cursor:pointer">Parameters</summary>
1715
+
1716
+ | Parameter | Type | Default | Description |
1717
+ |-----------|-------|---------|-----------------------------|
1718
+ | `array` | `V[]` | - | The array of items to manage selection for. |
1719
+ | `initial` | `V[]` | [] | The initial selection state. |
1720
+
1721
+ </details>
1722
+
1723
+ ---
1724
+
1725
+ <details>
1726
+
1727
+ <summary style="cursor:pointer">Returns</summary>
1728
+
1729
+ An object containing the selection state and handlers.
1730
+
1731
+ - `selection`: `V[]` - The current selected items.
1732
+ - `hasSelection`: `boolean` - Indicates whether `selection` is not empty. Short-hand for `selection.length > 0`.
1733
+ - `isSelected`: `IsSelectedHandler<V>` - Check if the given `entry` is in the selection.
1734
+ - `setSelection`: `SetSelectionHandler<V>` - A React Dispatch SetStateAction that allows custom selection update.
1735
+ - `select`: `SelectHandler<V>` - Update selection by adding a new `entry` or removing the given `entry` if already exists in the selection.
1736
+ - `groupSelect`: `GroupSelectHandler<V>` - Select all items from the given `array` starting from the first item in the selection up to the given `entry`.
1737
+ - `selectAll`: `SelectAllHandler` - Add all entries from the given `array` to the selection.
1738
+ - `resetSelection`: `ResetSelectionHandler` - Removes all entries from the selection.
1739
+
1740
+ </details>
1741
+
1742
+ ---
1743
+
1744
+ <details>
1745
+
1746
+ <summary style="cursor:pointer">Usage</summary>
1747
+
1748
+ ```tsx
1749
+ 'use client'
1750
+
1751
+ import { useCallback, useMemo } from 'react'
1752
+ import { useSelection } from '@alessiofrittoli/react-hooks'
1753
+
1754
+ interface Item
1755
+ {
1756
+ id : number
1757
+ name : string
1758
+ }
1759
+
1760
+ const items: Item[] = [
1761
+ {
1762
+ id : 1,
1763
+ name : 'item-1',
1764
+ },
1765
+ {
1766
+ id : 2,
1767
+ name : 'item-2',
1768
+ },
1769
+ {
1770
+ id : 3,
1771
+ name : 'item-3',
1772
+ },
1773
+ {
1774
+ id : 4,
1775
+ name : 'item-4',
1776
+ },
1777
+ {
1778
+ id : 5,
1779
+ name : 'item-5',
1780
+ },
1781
+ ]
1782
+
1783
+
1784
+ const MyComponent: React.FC = () => {
1785
+
1786
+ const {
1787
+ setSelection, select, groupSelect, isSelected
1788
+ } = useSelection( useMemo( () => items.map( item => item.id ), [] ) )
1789
+
1790
+ const clickHandler = useCallback( ( id: Item[ 'id' ] ) => (
1791
+ ( event: React.MouseEvent<HTMLButtonElement> ) => {
1792
+ if ( event.shiftKey ) {
1793
+ return groupSelect( id ) // group select
1794
+ }
1795
+ if ( event.metaKey || event.ctrlKey ) {
1796
+ return select( id ) // toggle single item in selection
1797
+ }
1798
+ setSelection( prev => (
1799
+ prev.includes( id ) ? [] : [ id ] // toggle single item selection
1800
+ ) )
1801
+ }
1802
+ ), [ select, groupSelect, setSelection ] )
1803
+
1804
+ return (
1805
+ <ul>
1806
+ { items.map( item => (
1807
+ <li key={ item.id }>
1808
+ <button
1809
+ onClick={ clickHandler( item.id ) }
1810
+ style={ {
1811
+ border: isSelected( item.id ) ? '1px solid red' : ' 1px solid black'
1812
+ } }
1813
+ >{ item.name }</button>
1814
+ </li>
1815
+ ) ) }
1816
+ </ul>
1817
+ )
1818
+
1819
+ }
1820
+ ```
1821
+
1822
+ </details>
1823
+
1824
+ ---
1825
+
1826
+ #### Timers
1827
+
1828
+ #### `useDebounce`
1829
+
1830
+ Debounce a value by a specified delay.
1831
+
1832
+ This hook returns a debounced version of the input value, which only updates
1833
+ after the specified delay has passed without any changes to the input value.
1834
+
1835
+ It is useful for scenarios like search input fields or other cases where
1836
+ frequent updates should be minimized.
1837
+
1838
+ The `Timeout` automatically restarts when the given `value` changes.
1839
+
1840
+ <details>
1841
+
1842
+ <summary style="cursor:pointer">Type Parameters</summary>
1843
+
1844
+ | Parameter | Description |
1845
+ |-----------|--------------------------|
1846
+ | `T` | The type of the `value`. |
1847
+
1848
+ </details>
1849
+
1850
+ ---
1851
+
1852
+ <details>
1853
+
1854
+ <summary style="cursor:pointer">Parameters</summary>
1855
+
1856
+ | Parameter | Type | Default | Description |
1857
+ |-----------|----------|---------|-----------------------------|
1858
+ | `value` | `T` | - | The value to debounce. This can be of any type. |
1859
+ | `delay` | `number` | 500 | The debounce delay in milliseconds. |
1860
+
1861
+ </details>
1862
+
1863
+ ---
1864
+
1865
+ <details>
1866
+
1867
+ <summary style="cursor:pointer">Returns</summary>
1868
+
1869
+ Type: `T`
1870
+
1871
+ The debounced value, which updates only after the delay has passed.
1872
+
1873
+ </details>
1874
+
1875
+ ---
1876
+
1877
+ <details>
1878
+
1879
+ <summary style="cursor:pointer">Usage</summary>
1880
+
1881
+ ```tsx
1882
+ 'use client'
1883
+
1884
+ import { useEffect, useState } from 'react'
1885
+ import { useDebounce } from '@alessiofrittoli/react-hooks'
1886
+
1887
+ const MyComponent: React.FC = () => {
1888
+
1889
+ const [ query, setQuery ] = useState( '' )
1890
+ const debouncedQuery = useDebounce( query )
1891
+
1892
+ useEffect( () => {
1893
+ if ( ! debouncedQuery ) return
1894
+
1895
+ fetch( '...', {
1896
+ // ...
1897
+ body: JSON.stringify( { query: debouncedQuery } )
1898
+ } )
1899
+
1900
+ }, [ debouncedQuery ] )
1901
+
1902
+ return (
1903
+ <input
1904
+ onChange={ event => setQuery( event.target.value ) }
1905
+ />
1906
+ )
1907
+
1908
+ }
1909
+ ```
1910
+
1911
+ </details>
1912
+
1913
+ ---
1914
+
1915
+ #### `useInterval`
1916
+
1917
+ Schedules repeated execution of `callback` every `delay` milliseconds.
1918
+
1919
+ When `delay` is larger than `2147483647` or less than `1` or `NaN`, the `delay` will be set to `1`. Non-integer delays are truncated to an integer.
1920
+ If `callback` is not a function, a `TypeError` will be thrown.
1921
+
1922
+ The `Timeout` is automatically cancelled on unmount.
1923
+
1924
+ <details>
1925
+
1926
+ <summary style="cursor:pointer">Type Parameters</summary>
1927
+
1928
+ | Parameter | Description |
1929
+ |-----------|--------------------------|
1930
+ | `T` | An Array defining optional arguments passed to the `callback`. |
1931
+
1932
+ </details>
1933
+
1934
+ ---
1935
+
1936
+ <details>
1937
+
1938
+ <summary style="cursor:pointer">Parameters</summary>
1939
+
1940
+ | Parameter | Type | Default | Description |
1941
+ |-----------|----------|---------|-----------------------------|
1942
+ | `callback`| `TimerHandler<T>` | - | The function to call when the timer elapses. |
1943
+ | `options` | `TimerOptions<T>` | - | (Optional) An object defining custom timer options. |
1944
+ | `options.delay` | `number` | `1` | The number of milliseconds to wait before calling the `callback`. |
1945
+ | `options.args` | `T` | - | Optional arguments to pass when the `callback` is called. |
1946
+ | `options.autoplay` | `boolean` | `true` | Indicates whether auto start the timer. |
1947
+ | `options.updateState` | `boolean` | `false` | Whether to update React state about Timer running status. |
1948
+ | `options.runOnStart` | `boolean` | `false` | Indicates whether to execute the callback when timer starts. |
1949
+
1950
+ </details>
1951
+
1952
+ ---
1953
+
1954
+ <details>
1955
+
1956
+ <summary style="cursor:pointer">Returns</summary>
1957
+
1958
+ Type: `TimerReturnType | StateTimerReturnType`
1959
+
1960
+ An object with timer utilities.
1961
+
1962
+ - start: `StartTimer` - Manually start the timer.
1963
+ - stop: `StopTimer` - Manually stop the timer.
1964
+
1965
+ If `updateState` is set to `true` then the following property is added in the returned object.
1966
+
1967
+ - isActive: `boolean` - Indicates whether the timer is active.
1968
+
1969
+ </details>
1970
+
1971
+ ---
1972
+
1973
+ <details>
1974
+
1975
+ <summary style="cursor:pointer">Usage</summary>
1976
+
1977
+ ##### Basic usage
1978
+
1979
+ ```tsx
1980
+ 'use client'
1981
+
1982
+ import { useCallback } from 'react'
1983
+ import { useInterval } from '@alessiofrittoli/react-hooks'
1984
+
1985
+ const MyComponent: React.FC = () => {
1986
+
1987
+ const { stop } = useInterval( useCallback( () => {
1988
+ console.log( 'tick timer' )
1989
+ }, [] ), { delay: 1000 } )
1990
+
1991
+ return (
1992
+ <button onClick={ stop }>Stop timer</button>
1993
+ )
1994
+
1995
+ }
1996
+ ```
1997
+
1998
+ ---
1999
+
2000
+ ##### Rely on state updates
2001
+
2002
+ ```tsx
2003
+ 'use client'
2004
+
2005
+ import { useCallback } from 'react'
2006
+ import { useInterval } from '@alessiofrittoli/react-hooks'
2007
+
2008
+ const MyComponent: React.FC = () => {
2009
+
2010
+ const { isActive, start, stop } = useInterval( useCallback( () => {
2011
+ console.log( 'tick timer' )
2012
+ }, [] ), {
2013
+ delay : 1000,
2014
+ autoplay : false,
2015
+ runOnStart : true,
2016
+ updateState : true,
2017
+ } )
2018
+
2019
+ return (
2020
+ <>
2021
+ { ! isActive && (
2022
+ <button onClick={ start }>Start timer</button>
2023
+ ) }
2024
+ { isActive && (
2025
+ <button onClick={ stop }>Stop timer</button>
2026
+ ) }
2027
+ </>
2028
+ )
2029
+
2030
+ }
2031
+ ```
2032
+
2033
+ </details>
2034
+
2035
+ ---
2036
+
2037
+ #### `useIntervalWhenVisible`
2038
+
2039
+ Schedules repeated execution of `callback` every `delay` milliseconds when `Document` is visible.
2040
+
2041
+ This hook automatically starts and stops the interval based on the `Document` visibility.
2042
+
2043
+ This hook has the same API of [`useInterval`](#useinterval) and automatically starts and stops timers based on `Document` visibility.
2044
+ Refer to [`useInterval`](#useinterval) API Reference for more info.
2045
+
2046
+ ---
2047
+
2048
+ #### `useLightInterval`
2049
+
2050
+ Schedules repeated execution of `callback` every `delay` milliseconds.
2051
+
2052
+ This is a lighter version of [`useInterval`](#useinterval) and is suggested to use when a basic functionality is enough (no manual start/stop or state updates).
2053
+
2054
+ <details>
2055
+
2056
+ <summary style="cursor:pointer">Type Parameters</summary>
2057
+
2058
+ | Parameter | Description |
2059
+ |-----------|--------------------------|
2060
+ | `T` | An Array defining optional arguments passed to the `callback`. |
2061
+
2062
+ </details>
2063
+
2064
+ ---
2065
+
2066
+ <details>
2067
+
2068
+ <summary style="cursor:pointer">Parameters</summary>
2069
+
2070
+ | Parameter | Type | Default | Description |
2071
+ |-----------|----------|---------|-----------------------------|
2072
+ | `callback`| `TimerHandler<T>` | - | The function to call when the timer elapses. |
2073
+ | `options` | `BasicTimerOptions<T>` | - | (Optional) An object defining custom timer options. |
2074
+ | `options.delay` | `number` | `1` | The number of milliseconds to wait before calling the `callback`. |
2075
+ | `options.args` | `T` | - | Optional arguments to pass when the `callback` is called. |
2076
+
2077
+ </details>
2078
+
2079
+ ---
2080
+
2081
+ <details>
2082
+
2083
+ <summary style="cursor:pointer">Usage</summary>
2084
+
2085
+ ```tsx
2086
+ 'use client'
2087
+
2088
+ import { useCallback } from 'react'
2089
+ import { useLightInterval } from '@alessiofrittoli/react-hooks'
2090
+
2091
+ const MyComponent: React.FC = () => {
2092
+
2093
+ useLightInterval( useCallback( () => {
2094
+ console.log( 'tick timer' )
2095
+ }, [] ), { delay: 1000 } )
2096
+
2097
+ }
2098
+ ```
2099
+
2100
+ </details>
2101
+
2102
+ ---
2103
+
2104
+ #### `useTimeout`
2105
+
2106
+ Schedules execution of a one-time `callback` after `delay` milliseconds.
2107
+
2108
+ The `callback` will likely not be invoked in precisely `delay` milliseconds.
2109
+
2110
+ Node.js makes no guarantees about the exact timing of when callbacks will fire,
2111
+ nor of their ordering. The callback will be called as close as possible to the
2112
+ time specified.
2113
+
2114
+ When `delay` is larger than `2147483647` or less than `1` or `NaN`, the `delay`
2115
+ will be set to `1`. Non-integer delays are truncated to an integer.
2116
+
2117
+ If `callback` is not a function, a `TypeError` will be thrown.
2118
+
2119
+ The `Timeout` is automatically cancelled on unmount.
2120
+
2121
+ <details>
2122
+
2123
+ <summary style="cursor:pointer">Type Parameters</summary>
2124
+
2125
+ | Parameter | Description |
2126
+ |-----------|--------------------------|
2127
+ | `T` | An Array defining optional arguments passed to the `callback`. |
2128
+
2129
+ </details>
2130
+
2131
+ ---
2132
+
2133
+ <details>
2134
+
2135
+ <summary style="cursor:pointer">Parameters</summary>
2136
+
2137
+ | Parameter | Type | Default | Description |
2138
+ |-----------|----------|---------|-----------------------------|
2139
+ | `callback`| `TimerHandler<T>` | - | The function to call when the timer elapses. |
2140
+ | `options` | `TimerOptions<T>` | - | (Optional) An object defining custom timer options. |
2141
+ | `options.delay` | `number` | `1` | The number of milliseconds to wait before calling the `callback`. |
2142
+ | `options.args` | `T` | - | Optional arguments to pass when the `callback` is called. |
2143
+ | `options.autoplay` | `boolean` | `true` | Indicates whether auto start the timer. |
2144
+ | `options.updateState` | `boolean` | `false` | Whether to update React state about Timer running status. |
2145
+ | `options.runOnStart` | `boolean` | `false` | Indicates whether to execute the callback when timer starts. |
2146
+
2147
+ </details>
2148
+
2149
+ ---
2150
+
2151
+ <details>
2152
+
2153
+ <summary style="cursor:pointer">Returns</summary>
2154
+
2155
+ Type: `TimerReturnType | StateTimerReturnType`
2156
+
2157
+ An object with timer utilities.
2158
+
2159
+ - start: `StartTimer` - Manually start the timer.
2160
+ - stop: `StopTimer` - Manually stop the timer.
2161
+
2162
+ If `updateState` is set to `true` then the following property is added in the returned object.
2163
+
2164
+ - isActive: `boolean` - Indicates whether the timer is active.
2165
+
2166
+ </details>
2167
+
2168
+ ---
2169
+
2170
+ <details>
2171
+
2172
+ <summary style="cursor:pointer">Usage</summary>
2173
+
2174
+ ##### Basic usage
2175
+
2176
+ ```tsx
2177
+ 'use client'
2178
+
2179
+ import { useCallback } from 'react'
2180
+ import { useTimeout } from '@alessiofrittoli/react-hooks'
2181
+
2182
+ const MyComponent: React.FC = () => {
2183
+
2184
+ const { stop } = useTimeout( useCallback( () => {
2185
+ console.log( 'tick timer' )
2186
+ }, [] ), { delay: 1000 } )
2187
+
2188
+ return (
2189
+ <button onClick={ stop }>Stop timer</button>
2190
+ )
2191
+
2192
+ }
2193
+ ```
2194
+
2195
+ ---
2196
+
2197
+ ##### Rely on state updates
2198
+
2199
+ ```tsx
2200
+ 'use client'
2201
+
2202
+ import { useCallback } from 'react'
2203
+ import { useTimeout } from '@alessiofrittoli/react-hooks'
2204
+
2205
+ const MyComponent: React.FC = () => {
2206
+
2207
+ const { isActive, start, stop } = useTimeout( useCallback( () => {
2208
+ console.log( 'tick timer' )
2209
+ }, [] ), {
2210
+ delay : 1000,
2211
+ autoplay : false,
2212
+ runOnStart : true,
2213
+ updateState : true,
2214
+ } )
2215
+
2216
+ return (
2217
+ <>
2218
+ { ! isActive && (
2219
+ <button onClick={ start }>Start timer</button>
2220
+ ) }
2221
+ { isActive && (
2222
+ <button onClick={ stop }>Stop timer</button>
2223
+ ) }
2224
+ </>
2225
+ )
2226
+
2227
+ }
2228
+ ```
2229
+
2230
+ </details>
2231
+
2232
+ ---
2233
+
2234
+ #### `useLightTimeout`
2235
+
2236
+ Schedules execution of a one-time `callback` after `delay` milliseconds.
2237
+
2238
+ This is a lighter version of [`useTimeout`](#usetimeout) and is suggested to use when a basic functionality is enough (no manual start/stop or state updates).
2239
+
2240
+ <details>
2241
+
2242
+ <summary style="cursor:pointer">Type Parameters</summary>
2243
+
2244
+ | Parameter | Description |
2245
+ |-----------|--------------------------|
2246
+ | `T` | An Array defining optional arguments passed to the `callback`. |
2247
+
2248
+ </details>
2249
+
2250
+ ---
2251
+
2252
+ <details>
2253
+
2254
+ <summary style="cursor:pointer">Parameters</summary>
2255
+
2256
+ | Parameter | Type | Default | Description |
2257
+ |-----------|----------|---------|-----------------------------|
2258
+ | `callback`| `TimerHandler<T>` | - | The function to call when the timer elapses. |
2259
+ | `options` | `BasicTimerOptions<T>` | - | (Optional) An object defining custom timer options. |
2260
+ | `options.delay` | `number` | `1` | The number of milliseconds to wait before calling the `callback`. |
2261
+ | `options.args` | `T` | - | Optional arguments to pass when the `callback` is called. |
2262
+
2263
+ </details>
2264
+
2265
+ ---
2266
+
2267
+ <details>
2268
+
2269
+ <summary style="cursor:pointer">Usage</summary>
2270
+
2271
+ ```tsx
2272
+ 'use client'
2273
+
2274
+ import { useCallback } from 'react'
2275
+ import { useLightTimeout } from '@alessiofrittoli/react-hooks'
2276
+
2277
+ const MyComponent: React.FC = () => {
2278
+
2279
+ useLightTimeout( useCallback( () => {
2280
+ console.log( 'tick timer' )
2281
+ }, [] ), { delay: 1000 } )
2282
+
2283
+ }
2284
+ ```
2285
+
2286
+ </details>
1145
2287
 
1146
2288
  ---
1147
2289