From 09fe1e813bcb704a6703d5d9b94b61ab406ba279 Mon Sep 17 00:00:00 2001 From: Leroy Korterink Date: Tue, 4 Apr 2023 12:30:15 +0200 Subject: [PATCH] Update event listener hooks (#118) * Update event listener hooks - Remove useEventListener function overloads - Remove useDocument hook - Remove useWindow hook - Add useDocument hook - Add useWindow hook - Add RefObjectOption to useEventListener for ref support #101 #102 * Remove invalid parameters in docs * Create isRefObject function See: https://github.com/mediamonks/react-hooks/pull/118#discussion_r1155741760 * Rename getRefObjectOption to unref * Remove unnecessary useDocument and useWindow hooks * Update src/utils/unref/unref.mdx Co-authored-by: Arjan van Wijk --------- Co-authored-by: Arjan van Wijk --- .../useDocumentEventListener.stories.mdx | 26 - .../useDocumentEventListener.stories.tsx | 38 - .../useDocumentEventListener.test.tsx | 18 - .../useDocumentEventListener.ts | 12 - .../useEventListener.stories.mdx | 30 +- .../useEventListener.stories.tsx | 4 +- .../useEventListener.test.tsx | 56 +- .../useEventListener/useEventListener.ts | 1688 +---------------- src/hooks/useHasFocus/useHasFocus.ts | 6 +- .../useMediaQuery/useMediaQuery.component.tsx | 2 +- .../useMediaQuery.playwright.tsx | 14 +- src/hooks/useMediaQuery/useMediaQuery.ts | 4 +- src/hooks/useRefValue/useRefValue.ts | 2 +- .../useWindowEventListener.stories.mdx | 30 - .../useWindowEventListener.stories.tsx | 33 - .../useWindowEventListener.test.tsx | 20 - .../useWindowEventListener.ts | 12 - src/index.ts | 8 +- src/utils/isRefObject/isRefObject.mdx | 25 + src/utils/isRefObject/isRefObject.test.ts | 37 + src/utils/isRefObject/isRefObject.ts | 8 + src/utils/unref/unref.mdx | 53 + src/utils/unref/unref.test.ts | 23 + src/utils/unref/unref.ts | 19 + 24 files changed, 267 insertions(+), 1901 deletions(-) delete mode 100644 src/hooks/useDocumentEventListener/useDocumentEventListener.stories.mdx delete mode 100644 src/hooks/useDocumentEventListener/useDocumentEventListener.stories.tsx delete mode 100644 src/hooks/useDocumentEventListener/useDocumentEventListener.test.tsx delete mode 100644 src/hooks/useDocumentEventListener/useDocumentEventListener.ts delete mode 100644 src/hooks/useWindowEventListener/useWindowEventListener.stories.mdx delete mode 100644 src/hooks/useWindowEventListener/useWindowEventListener.stories.tsx delete mode 100644 src/hooks/useWindowEventListener/useWindowEventListener.test.tsx delete mode 100644 src/hooks/useWindowEventListener/useWindowEventListener.ts create mode 100644 src/utils/isRefObject/isRefObject.mdx create mode 100644 src/utils/isRefObject/isRefObject.test.ts create mode 100644 src/utils/isRefObject/isRefObject.ts create mode 100644 src/utils/unref/unref.mdx create mode 100644 src/utils/unref/unref.test.ts create mode 100644 src/utils/unref/unref.ts diff --git a/src/hooks/useDocumentEventListener/useDocumentEventListener.stories.mdx b/src/hooks/useDocumentEventListener/useDocumentEventListener.stories.mdx deleted file mode 100644 index ddfc3e8..0000000 --- a/src/hooks/useDocumentEventListener/useDocumentEventListener.stories.mdx +++ /dev/null @@ -1,26 +0,0 @@ -import { Meta } from '@storybook/blocks'; - - - -# useDocumentEventListener - -This hook allows you to add a document event listener and remove it when the component unmounts. - -## Reference - -```ts -export function useDocumentEventListener( - event: T, - callback: (event: DocumentEventMap[T]) => void, -): void; -``` - -## Usage - -```tsx -const [keydown, setKeydown] = useState>([]); - -useDocumentEventListener('keydown', (event) => { - setKeydown((previous) => [...previous, event.key]); -}); -``` diff --git a/src/hooks/useDocumentEventListener/useDocumentEventListener.stories.tsx b/src/hooks/useDocumentEventListener/useDocumentEventListener.stories.tsx deleted file mode 100644 index a85652d..0000000 --- a/src/hooks/useDocumentEventListener/useDocumentEventListener.stories.tsx +++ /dev/null @@ -1,38 +0,0 @@ -/* eslint-disable react/jsx-no-literals */ -import type { StoryObj } from '@storybook/react'; -import { type ReactElement, useState } from 'react'; -import { useDocumentEventListener } from './useDocumentEventListener.js'; - -export default { - title: 'hooks/useDocumentEventListener', -}; - -function DemoComponent(): ReactElement { - const [keydown, setKeydown] = useState>([]); - - useDocumentEventListener('keydown', (event) => { - setKeydown((previous) => [...previous, event.key]); - }); - - return ( -
-
-

Instructions!

-

Press a key to trigger the document keydown event.

-
- -
    - {keydown.map((key, index) => ( - // eslint-disable-next-line react/no-array-index-key -
  • {key}
  • - ))} -
-
- ); -} - -export const Demo: StoryObj = { - render() { - return ; - }, -}; diff --git a/src/hooks/useDocumentEventListener/useDocumentEventListener.test.tsx b/src/hooks/useDocumentEventListener/useDocumentEventListener.test.tsx deleted file mode 100644 index bc18627..0000000 --- a/src/hooks/useDocumentEventListener/useDocumentEventListener.test.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { renderHook } from '@testing-library/react'; -import { useDocumentEventListener } from './useDocumentEventListener.js'; - -describe('useDocumentEventListener', () => { - it('should not crash', () => { - renderHook( - () => { - useDocumentEventListener('focusin', () => { - // eslint-disable-next-line no-console - console.log(document.activeElement); - }); - }, - { - initialProps: undefined, - }, - ); - }); -}); diff --git a/src/hooks/useDocumentEventListener/useDocumentEventListener.ts b/src/hooks/useDocumentEventListener/useDocumentEventListener.ts deleted file mode 100644 index e7017b1..0000000 --- a/src/hooks/useDocumentEventListener/useDocumentEventListener.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { useEventListener } from '../useEventListener/useEventListener.js'; - -/** - * SSR-safe hook that adds an event listener to the document. - */ -export function useDocumentEventListener( - type: K, - listener: (this: Document, event: DocumentEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void { - useEventListener(typeof document === 'undefined' ? undefined : document, type, listener, options); -} diff --git a/src/hooks/useEventListener/useEventListener.stories.mdx b/src/hooks/useEventListener/useEventListener.stories.mdx index 25eadb4..0c38cdd 100644 --- a/src/hooks/useEventListener/useEventListener.stories.mdx +++ b/src/hooks/useEventListener/useEventListener.stories.mdx @@ -10,28 +10,42 @@ removed when the component unmounts. ## Reference ```ts -export function useEventListener( - target: T | undefined, - event: K, - listener: (this: EventTarget, event: EventMap[K]) => void, - options?: boolean | EventListenerOptions, +function useEventListener( + targetOption: RefObject | T | null | undefined, + type: string, + listener: EventListener, + options?: boolean | AddEventListenerOptions, ): void; ``` ## Usage -## Server-size rendering +Using a RefObject to attach an event to a DOM element. + +```tsx +function MyComponent() { + const ref = useRef(null); + + useEventListener(ref, 'click', (event) => { + ... + }); + + return
...
; +} +``` + +### Server-size rendering The hook doesn't throw errors when the target is undefined to support server-side rendering. ```tsx -useEventListener(typeof document === 'undefined' ? undefined : document, 'focusin', (event) => { +useEventListener(globalThis.document, 'focusin', (event) => { ... }); ``` ```tsx -useEventListener(typeof window === 'undefined' ? undefined : window, 'resize', (event) => { +useEventListener(globalThis.window, 'resize', (event) => { ... }); ``` diff --git a/src/hooks/useEventListener/useEventListener.stories.tsx b/src/hooks/useEventListener/useEventListener.stories.tsx index 186565e..4e847bd 100644 --- a/src/hooks/useEventListener/useEventListener.stories.tsx +++ b/src/hooks/useEventListener/useEventListener.stories.tsx @@ -1,6 +1,6 @@ /* eslint-disable react/jsx-no-literals */ import type { StoryObj } from '@storybook/react'; -import { type ReactElement, useState } from 'react'; +import { useState, type ReactElement } from 'react'; import { useEventListener } from './useEventListener.js'; export default { @@ -10,7 +10,7 @@ export default { function DemoComponent(): ReactElement { const [text, setText] = useState>([]); - useEventListener(typeof document === 'undefined' ? undefined : document, 'focusin', (event) => { + useEventListener(globalThis.document, 'focusin', (event) => { // eslint-disable-next-line no-console setText((previous) => [ ...previous, diff --git a/src/hooks/useEventListener/useEventListener.test.tsx b/src/hooks/useEventListener/useEventListener.test.tsx index 725190d..8d210ca 100644 --- a/src/hooks/useEventListener/useEventListener.test.tsx +++ b/src/hooks/useEventListener/useEventListener.test.tsx @@ -1,18 +1,48 @@ -import { renderHook } from '@testing-library/react'; +/* eslint-disable react/no-multi-comp */ +import { jest } from '@jest/globals'; +import { render } from '@testing-library/react'; +import { createRef, useEffect, useState, type ReactElement } from 'react'; import { useEventListener } from './useEventListener.js'; describe('useEventListener', () => { - it('should not crash', () => { - renderHook( - () => { - useEventListener(typeof document === 'undefined' ? undefined : document, 'focusin', () => { - // eslint-disable-next-line no-console - console.log(document.activeElement); - }); - }, - { - initialProps: undefined, - }, - ); + it('Should listen to event attached to element from RefObject', () => { + const spy = jest.fn(); + const ref = createRef(); + + function Test(): ReactElement { + useEventListener(ref, 'click', spy); + + return
; + } + + render(); + + ref.current?.click(); + + expect(spy).toBeCalledTimes(1); + }); + + it('Should listen to event attached to element from state', async () => { + const spy = jest.fn(); + let exposedRef: HTMLDivElement | null = null; + + function Test(): ReactElement { + const [ref, setRef] = useState(null); + + useEffect(() => { + exposedRef = ref; + }, [ref]); + + useEventListener(ref, 'click', spy); + + return
; + } + + render(); + + // @ts-expect-error typescript doesn't infer type for exposedRef correctly + exposedRef?.click(); + + expect(spy).toBeCalledTimes(1); }); }); diff --git a/src/hooks/useEventListener/useEventListener.ts b/src/hooks/useEventListener/useEventListener.ts index ef52e39..612796e 100644 --- a/src/hooks/useEventListener/useEventListener.ts +++ b/src/hooks/useEventListener/useEventListener.ts @@ -1,1679 +1,27 @@ -/* eslint-disable @typescript-eslint/unified-signatures */ -import { useEffect } from 'react'; +/* eslint-disable @typescript-eslint/unified-signatures, @typescript-eslint/no-explicit-any */ +import { useEffect, type RefObject } from 'react'; +import { unref } from '../../utils/unref/unref.js'; +import { useRefValue } from '../useRefValue/useRefValue.js'; -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: AbstractWorker, event: AbstractWorkerEventMap[K]) => void, +export function useEventListener( + targetOption: RefObject | T | null | undefined, + type: string, + listener: EventListener, options?: boolean | AddEventListenerOptions, -): void; - -// @ts-expect-error function overload is correct but typescript doesn't like it -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: Animation, event: AnimationEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends AudioBufferSourceNode, - K extends keyof AudioScheduledSourceNodeEventMap, ->( - target: T | undefined, - type: K, - listener: (this: AudioBufferSourceNode, event: AudioScheduledSourceNodeEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: AudioContext, event: BaseAudioContextEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends AudioScheduledSourceNode, - K extends keyof AudioScheduledSourceNodeEventMap, ->( - target: T | undefined, - type: K, - listener: (this: AudioScheduledSourceNode, event: AudioScheduledSourceNodeEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends AudioWorkletNode, - K extends keyof AudioWorkletNodeEventMap, ->( - target: T | undefined, - type: K, - listener: (this: AudioWorkletNode, event: AudioWorkletNodeEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends BaseAudioContext, - K extends keyof BaseAudioContextEventMap, ->( - target: T | undefined, - type: K, - listener: (this: BaseAudioContext, event: BaseAudioContextEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends BroadcastChannel, - K extends keyof BroadcastChannelEventMap, ->( - target: T | undefined, - type: K, - listener: (this: BroadcastChannel, event: BroadcastChannelEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: CSSAnimation, event: AnimationEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: CSSTransition, event: AnimationEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends CanvasCaptureMediaStreamTrack, - K extends keyof MediaStreamTrackEventMap, ->( - target: T | undefined, - type: K, - listener: (this: CanvasCaptureMediaStreamTrack, event: MediaStreamTrackEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends ConstantSourceNode, - K extends keyof AudioScheduledSourceNodeEventMap, ->( - target: T | undefined, - type: K, - listener: (this: ConstantSourceNode, event: AudioScheduledSourceNodeEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: Document, event: DocumentEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends DocumentAndElementEventHandlers, - K extends keyof DocumentAndElementEventHandlersEventMap, ->( - target: T | undefined, - type: K, - listener: ( - this: DocumentAndElementEventHandlers, - event: DocumentAndElementEventHandlersEventMap[K], - ) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: Element, event: ElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: EventSource, event: EventSourceEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: FileReader, event: FileReaderEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: FontFaceSet, event: FontFaceSetEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends GlobalEventHandlers, - K extends keyof GlobalEventHandlersEventMap, ->( - target: T | undefined, - type: K, - listener: (this: GlobalEventHandlers, event: GlobalEventHandlersEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLAnchorElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLAreaElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends HTMLAudioElement, - K extends keyof HTMLMediaElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: HTMLAudioElement, event: HTMLMediaElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLBRElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLBaseElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends HTMLBodyElement, - K extends keyof HTMLBodyElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: HTMLBodyElement, event: HTMLBodyElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLButtonElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLCanvasElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLDListElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLDataElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends HTMLDataListElement, - K extends keyof HTMLElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: HTMLDataListElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLDetailsElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLDialogElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLDivElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLEmbedElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends HTMLFieldSetElement, - K extends keyof HTMLElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: HTMLFieldSetElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLFormElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLHRElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLHeadElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLHeadingElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLHtmlElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLIFrameElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLImageElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLInputElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLLIElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLLabelElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLLegendElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLLinkElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLMapElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends HTMLMediaElement, - K extends keyof HTMLMediaElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: HTMLMediaElement, event: HTMLMediaElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLMenuElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLMetaElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLMeterElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLModElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLOListElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLObjectElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends HTMLOptGroupElement, - K extends keyof HTMLElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: HTMLOptGroupElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLOptionElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLOutputElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends HTMLParagraphElement, - K extends keyof HTMLElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: HTMLParagraphElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLPictureElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLPreElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends HTMLProgressElement, - K extends keyof HTMLElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: HTMLProgressElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLQuoteElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLScriptElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLSelectElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLSlotElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLSourceElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLSpanElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLStyleElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends HTMLTableCaptionElement, - K extends keyof HTMLElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: HTMLTableCaptionElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends HTMLTableCellElement, - K extends keyof HTMLElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: HTMLTableCellElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends HTMLTableColElement, - K extends keyof HTMLElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: HTMLTableColElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLTableElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends HTMLTableRowElement, - K extends keyof HTMLElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: HTMLTableRowElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends HTMLTableSectionElement, - K extends keyof HTMLElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: HTMLTableSectionElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends HTMLTemplateElement, - K extends keyof HTMLElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: HTMLTemplateElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends HTMLTextAreaElement, - K extends keyof HTMLElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: HTMLTextAreaElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLTimeElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLTitleElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLTrackElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLUListElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLUnknownElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends HTMLVideoElement, - K extends keyof HTMLVideoElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: HTMLVideoElement, event: HTMLVideoElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: IDBDatabase, event: IDBDatabaseEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends IDBOpenDBRequest, - K extends keyof IDBOpenDBRequestEventMap, ->( - target: T | undefined, - type: K, - listener: (this: IDBOpenDBRequest, event: IDBOpenDBRequestEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: IDBRequest, event: IDBRequestEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: IDBTransaction, event: IDBTransactionEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: MathMLElement, event: MathMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: MediaDevices, event: MediaDevicesEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends MediaKeySession, - K extends keyof MediaKeySessionEventMap, ->( - target: T | undefined, - type: K, - listener: (this: MediaKeySession, event: MediaKeySessionEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: MediaQueryList, event: MediaQueryListEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: MediaRecorder, event: MediaRecorderEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: MediaSource, event: MediaSourceEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: MediaStream, event: MediaStreamEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends MediaStreamTrack, - K extends keyof MediaStreamTrackEventMap, ->( - target: T | undefined, - type: K, - listener: (this: MediaStreamTrack, event: MediaStreamTrackEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: MessagePort, event: MessagePortEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: Notification, event: NotificationEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends OfflineAudioContext, - K extends keyof OfflineAudioContextEventMap, ->( - target: T | undefined, - type: K, - listener: (this: OfflineAudioContext, event: OfflineAudioContextEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends OffscreenCanvas, - K extends keyof OffscreenCanvasEventMap, ->( - target: T | undefined, - type: K, - listener: (this: OffscreenCanvas, event: OffscreenCanvasEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends OscillatorNode, - K extends keyof AudioScheduledSourceNodeEventMap, ->( - target: T | undefined, - type: K, - listener: (this: OscillatorNode, event: AudioScheduledSourceNodeEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: PaymentRequest, event: PaymentRequestEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: Performance, event: PerformanceEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends PermissionStatus, - K extends keyof PermissionStatusEventMap, ->( - target: T | undefined, - type: K, - listener: (this: PermissionStatus, event: PermissionStatusEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends PictureInPictureWindow, - K extends keyof PictureInPictureWindowEventMap, ->( - target: T | undefined, - type: K, - listener: (this: PictureInPictureWindow, event: PictureInPictureWindowEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: RTCDTMFSender, event: RTCDTMFSenderEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: RTCDataChannel, event: RTCDataChannelEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends RTCDtlsTransport, - K extends keyof RTCDtlsTransportEventMap, ->( - target: T | undefined, - type: K, - listener: (this: RTCDtlsTransport, event: RTCDtlsTransportEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends RTCIceTransport, - K extends keyof RTCIceTransportEventMap, ->( - target: T | undefined, - type: K, - listener: (this: RTCIceTransport, event: RTCIceTransportEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends RTCPeerConnection, - K extends keyof RTCPeerConnectionEventMap, ->( - target: T | undefined, - type: K, - listener: (this: RTCPeerConnection, event: RTCPeerConnectionEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends RTCSctpTransport, - K extends keyof RTCSctpTransportEventMap, ->( - target: T | undefined, - type: K, - listener: (this: RTCSctpTransport, event: RTCSctpTransportEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: RemotePlayback, event: RemotePlaybackEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGAElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGAnimateElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends SVGAnimateMotionElement, - K extends keyof SVGElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: SVGAnimateMotionElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends SVGAnimateTransformElement, - K extends keyof SVGElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: SVGAnimateTransformElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGAnimationElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGCircleElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGClipPathElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends SVGComponentTransferFunctionElement, - K extends keyof SVGElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: SVGComponentTransferFunctionElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGDefsElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGDescElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGEllipseElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGFEBlendElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends SVGFEColorMatrixElement, - K extends keyof SVGElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: SVGFEColorMatrixElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends SVGFEComponentTransferElement, - K extends keyof SVGElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: SVGFEComponentTransferElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends SVGFECompositeElement, - K extends keyof SVGElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: SVGFECompositeElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends SVGFEConvolveMatrixElement, - K extends keyof SVGElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: SVGFEConvolveMatrixElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends SVGFEDiffuseLightingElement, - K extends keyof SVGElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: SVGFEDiffuseLightingElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends SVGFEDisplacementMapElement, - K extends keyof SVGElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: SVGFEDisplacementMapElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends SVGFEDistantLightElement, - K extends keyof SVGElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: SVGFEDistantLightElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends SVGFEDropShadowElement, - K extends keyof SVGElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: SVGFEDropShadowElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGFEFloodElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGFEFuncAElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGFEFuncBElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGFEFuncGElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGFEFuncRElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends SVGFEGaussianBlurElement, - K extends keyof SVGElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: SVGFEGaussianBlurElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGFEImageElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGFEMergeElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends SVGFEMergeNodeElement, - K extends keyof SVGElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: SVGFEMergeNodeElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends SVGFEMorphologyElement, - K extends keyof SVGElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: SVGFEMorphologyElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGFEOffsetElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends SVGFEPointLightElement, - K extends keyof SVGElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: SVGFEPointLightElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends SVGFESpecularLightingElement, - K extends keyof SVGElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: SVGFESpecularLightingElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends SVGFESpotLightElement, - K extends keyof SVGElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: SVGFESpotLightElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGFETileElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends SVGFETurbulenceElement, - K extends keyof SVGElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: SVGFETurbulenceElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGFilterElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends SVGForeignObjectElement, - K extends keyof SVGElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: SVGForeignObjectElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGGElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGGeometryElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGGradientElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGGraphicsElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGImageElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGLineElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends SVGLinearGradientElement, - K extends keyof SVGElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: SVGLinearGradientElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGMPathElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGMarkerElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGMaskElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGMetadataElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGPathElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGPatternElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGPolygonElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGPolylineElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends SVGRadialGradientElement, - K extends keyof SVGElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: SVGRadialGradientElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGRectElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGSVGElement, event: SVGSVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGScriptElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGSetElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGStopElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGStyleElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGSwitchElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGSymbolElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGTSpanElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends SVGTextContentElement, - K extends keyof SVGElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: SVGTextContentElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGTextElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGTextPathElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends SVGTextPositioningElement, - K extends keyof SVGElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: SVGTextPositioningElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGTitleElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGUseElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGViewElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends ScreenOrientation, - K extends keyof ScreenOrientationEventMap, ->( - target: T | undefined, - type: K, - listener: (this: ScreenOrientation, event: ScreenOrientationEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends ScriptProcessorNode, - K extends keyof ScriptProcessorNodeEventMap, ->( - target: T | undefined, - type: K, - listener: (this: ScriptProcessorNode, event: ScriptProcessorNodeEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: ServiceWorker, event: ServiceWorkerEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends ServiceWorkerContainer, - K extends keyof ServiceWorkerContainerEventMap, ->( - target: T | undefined, - type: K, - listener: (this: ServiceWorkerContainer, event: ServiceWorkerContainerEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends ServiceWorkerRegistration, - K extends keyof ServiceWorkerRegistrationEventMap, ->( - target: T | undefined, - type: K, - listener: (this: ServiceWorkerRegistration, event: ServiceWorkerRegistrationEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: ShadowRoot, event: ShadowRootEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SharedWorker, event: AbstractWorkerEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SourceBuffer, event: SourceBufferEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends SourceBufferList, - K extends keyof SourceBufferListEventMap, ->( - target: T | undefined, - type: K, - listener: (this: SourceBufferList, event: SourceBufferListEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends SpeechSynthesis, - K extends keyof SpeechSynthesisEventMap, ->( - target: T | undefined, - type: K, - listener: (this: SpeechSynthesis, event: SpeechSynthesisEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends SpeechSynthesisUtterance, - K extends keyof SpeechSynthesisUtteranceEventMap, ->( - target: T | undefined, - type: K, - listener: (this: SpeechSynthesisUtterance, event: SpeechSynthesisUtteranceEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: TextTrack, event: TextTrackEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: TextTrackCue, event: TextTrackCueEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: TextTrackList, event: TextTrackListEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: VTTCue, event: TextTrackCueEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: VisualViewport, event: VisualViewportEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: WebSocket, event: WebSocketEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: Window, event: WindowEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends WindowEventHandlers, - K extends keyof WindowEventHandlersEventMap, ->( - target: T | undefined, - type: K, - listener: (this: WindowEventHandlers, event: WindowEventHandlersEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: Worker, event: WorkerEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: XMLDocument, event: DocumentEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; +): void { + const listenerRef = useRefValue(listener); -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: XMLHttpRequest, event: XMLHttpRequestEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; + useEffect(() => { + const target = unref(targetOption); -export function useEventListener< - T extends XMLHttpRequestEventTarget, - K extends keyof XMLHttpRequestEventTargetEventMap, ->( - target: T | undefined, - type: K, - listener: (this: XMLHttpRequestEventTarget, event: XMLHttpRequestEventTargetEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; + function callback(this: unknown, event: Event): void { + listenerRef.current?.(event); + } -export function useEventListener< - T extends XMLHttpRequestUpload, - K extends keyof XMLHttpRequestEventTargetEventMap, ->( - target: T | undefined, - type: K, - listener: (this: XMLHttpRequestUpload, event: XMLHttpRequestEventTargetEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; + target?.addEventListener(type, callback, options); -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: Window, event: WindowEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void { - useEffect(() => { - target?.addEventListener(type, listener, options); return () => { - target?.removeEventListener(type, listener, options); + target?.removeEventListener(type, callback, options); }; - }, [target, type, listener, options]); + }, [listenerRef, options, targetOption, type]); } diff --git a/src/hooks/useHasFocus/useHasFocus.ts b/src/hooks/useHasFocus/useHasFocus.ts index 3f00675..ee94faf 100644 --- a/src/hooks/useHasFocus/useHasFocus.ts +++ b/src/hooks/useHasFocus/useHasFocus.ts @@ -1,5 +1,5 @@ import { useCallback, useState, type RefObject } from 'react'; -import { useDocumentEventListener } from '../useDocumentEventListener/useDocumentEventListener.js'; +import { useEventListener } from '../useEventListener/useEventListener.js'; export type FocusPseudoSelector = ':focus' | ':focus-visible' | ':focus-within'; @@ -21,8 +21,8 @@ export function useHasFocus( setState(matches()); }, [matches, setState]); - useDocumentEventListener('focusin', onUpdate); - useDocumentEventListener('focusout', onUpdate); + useEventListener(globalThis.document, 'focusin', onUpdate); + useEventListener(globalThis.document, 'focusout', onUpdate); return state; } diff --git a/src/hooks/useMediaQuery/useMediaQuery.component.tsx b/src/hooks/useMediaQuery/useMediaQuery.component.tsx index e1e7354..bbdc87f 100644 --- a/src/hooks/useMediaQuery/useMediaQuery.component.tsx +++ b/src/hooks/useMediaQuery/useMediaQuery.component.tsx @@ -1,7 +1,7 @@ import type { ReactElement } from 'react'; import { useMediaQuery } from './useMediaQuery.js'; -export function Component(): ReactElement { +export function UseMediaQueryComponent(): ReactElement { // @ts-expect-error variable is defined in index.html const isMinWidth480px = useMediaQuery('--min-width-480'); diff --git a/src/hooks/useMediaQuery/useMediaQuery.playwright.tsx b/src/hooks/useMediaQuery/useMediaQuery.playwright.tsx index 7b48ed9..ac7038b 100644 --- a/src/hooks/useMediaQuery/useMediaQuery.playwright.tsx +++ b/src/hooks/useMediaQuery/useMediaQuery.playwright.tsx @@ -1,5 +1,5 @@ import { expect, test } from '@playwright/experimental-ct-react'; -import { Component } from './useMediaQuery.component.js'; +import { UseMediaQueryComponent } from './useMediaQuery.component.js'; test('Should detect initial screen size', async ({ page, mount }) => { await page.setViewportSize({ @@ -7,13 +7,13 @@ test('Should detect initial screen size', async ({ page, mount }) => { height: 480, }); - const component = await mount(, {}); + const component = await mount(); await expect(component).toContainText('large'); }); test('Should detect screen size changes', async ({ page, mount }) => { - await mount(, {}); + await mount(); await page.setViewportSize({ width: 250, @@ -21,16 +21,12 @@ test('Should detect screen size changes', async ({ page, mount }) => { }); // Wait for the resize to be handled by the hook - await page.getByText('small').waitFor({ - timeout: 100, - }); + await page.getByText('small').waitFor(); await page.setViewportSize({ width: 480, height: 480, }); - await page.getByText('large').waitFor({ - timeout: 100, - }); + await page.getByText('large').waitFor(); }); diff --git a/src/hooks/useMediaQuery/useMediaQuery.ts b/src/hooks/useMediaQuery/useMediaQuery.ts index 710cb90..c408175 100644 --- a/src/hooks/useMediaQuery/useMediaQuery.ts +++ b/src/hooks/useMediaQuery/useMediaQuery.ts @@ -50,7 +50,9 @@ export function useMediaQuery(mediaQueryVariableName: MediaQueries, defaultValue }, [defaultValue, mediaQueryList?.matches, mediaQueryVariableName]); useEventListener(mediaQueryList, 'change', (event) => { - setMatches(event.matches); + if (event instanceof MediaQueryListEvent) { + setMatches(event.matches); + } }); return matches ?? defaultValue; diff --git a/src/hooks/useRefValue/useRefValue.ts b/src/hooks/useRefValue/useRefValue.ts index f852b86..7b370b8 100644 --- a/src/hooks/useRefValue/useRefValue.ts +++ b/src/hooks/useRefValue/useRefValue.ts @@ -3,7 +3,7 @@ import { type RefObject, useRef } from 'react'; /** * Keeps a ref up to date with a changing value. * - * Normal values are captured in scopes, while refs are not. Therefore a changing value in a callback + * Normal values are captured in scopes, while refs are not. Therefore a changing value in a callback * requires the use of a ref. * * @param value The value to keep in the ref. diff --git a/src/hooks/useWindowEventListener/useWindowEventListener.stories.mdx b/src/hooks/useWindowEventListener/useWindowEventListener.stories.mdx deleted file mode 100644 index 438bf8b..0000000 --- a/src/hooks/useWindowEventListener/useWindowEventListener.stories.mdx +++ /dev/null @@ -1,30 +0,0 @@ -import { Meta } from '@storybook/blocks'; - - - -# useWindowEventListener - -This hook allows you to add a window event listener and remove it when the component unmounts. - -## Reference - -```ts -function useWindowEventListener( - event: T, - callback: (event: WindowEventMap[T]) => void, -): void; -``` - -## Usage - -```tsx -function DemoComponent(): ReactElement { - const [size, setSize] = useState([0, 0]); - - useWindowEventListener('resize', () => { - setSize([window.innerWidth, window.innerHeight]); - }); - - return
{size.join('x')}
; -} -``` diff --git a/src/hooks/useWindowEventListener/useWindowEventListener.stories.tsx b/src/hooks/useWindowEventListener/useWindowEventListener.stories.tsx deleted file mode 100644 index 3e9d6be..0000000 --- a/src/hooks/useWindowEventListener/useWindowEventListener.stories.tsx +++ /dev/null @@ -1,33 +0,0 @@ -/* eslint-disable react/jsx-no-literals */ -import type { StoryObj } from '@storybook/react'; -import { type ReactElement, useState } from 'react'; -import { useWindowEventListener } from './useWindowEventListener.js'; - -export default { - title: 'hooks/useWindowEventListener', -}; - -function DemoComponent(): ReactElement { - const [size, setSize] = useState([0, 0]); - - useWindowEventListener('resize', () => { - setSize([window.innerWidth, window.innerHeight]); - }); - - return ( -
-
-

Instructions!

-

Resize the viewport to listen to the resize event on the window.

-
- -
Viewport size: {size.join('x')}
-
- ); -} - -export const Demo: StoryObj = { - render() { - return ; - }, -}; diff --git a/src/hooks/useWindowEventListener/useWindowEventListener.test.tsx b/src/hooks/useWindowEventListener/useWindowEventListener.test.tsx deleted file mode 100644 index e7d7551..0000000 --- a/src/hooks/useWindowEventListener/useWindowEventListener.test.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { jest } from '@jest/globals'; -import { renderHook } from '@testing-library/react'; -import { useWindowEventListener } from './useWindowEventListener.js'; - -describe('useWindowEventListener', () => { - it('should not crash', async () => { - const spy = jest.fn(); - - renderHook(() => { - useWindowEventListener('resize', () => { - spy(); - - // eslint-disable-next-line no-console - console.log('window resized'); - }); - }); - - expect(spy).not.toHaveBeenCalled(); - }); -}); diff --git a/src/hooks/useWindowEventListener/useWindowEventListener.ts b/src/hooks/useWindowEventListener/useWindowEventListener.ts deleted file mode 100644 index 20b7db6..0000000 --- a/src/hooks/useWindowEventListener/useWindowEventListener.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { useEventListener } from '../useEventListener/useEventListener.js'; - -/** - * SSR-safe hook that adds an event listener to the window. - */ -export function useWindowEventListener( - type: K, - listener: (this: Window, event: WindowEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void { - useEventListener(typeof window === 'undefined' ? undefined : window, type, listener, options); -} diff --git a/src/index.ts b/src/index.ts index 4721d3b..9ca20fd 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,14 +1,12 @@ /* PLOP_ADD_EXPORT */ -export * from './hooks/useRefValue/useRefValue.js'; export * from './components/AutoFill/AutoFill.js'; export * from './hocs/ensuredForwardRef/ensuredForwardRef.js'; export * from './hooks/useBeforeMount/useBeforeMount.js'; -export * from './hooks/useDocumentEventListener/useDocumentEventListener.js'; export * from './hooks/useEventListener/useEventListener.js'; export * from './hooks/useForceRerender/useForceRerender.js'; export * from './hooks/useHasFocus/useHasFocus.js'; -export * from './hooks/useIsMountedState/useIsMountedState.js'; export * from './hooks/useIsMounted/useIsMounted.js'; +export * from './hooks/useIsMountedState/useIsMountedState.js'; export * from './hooks/useIsomorphicLayoutEffect/useIsomorphicLayoutEffect.js'; export * from './hooks/useMediaQuery/useMediaQuery.js'; export * from './hooks/useMount/useMount.js'; @@ -17,10 +15,12 @@ export * from './hooks/useRefs/useRefs.types.js'; export * from './hooks/useRefs/utils/assertAndUnwrapRefs/assertAndUnwrapRefs.js'; export * from './hooks/useRefs/utils/unwrapRefs/unwrapRefs.js'; export * from './hooks/useRefs/utils/unwrapRefs/unwrapRefs.types.js'; +export * from './hooks/useRefValue/useRefValue.js'; export * from './hooks/useRegisterRef/useRegisterRef.js'; export * from './hooks/useResizeObserver/useResizeObserver.js'; export * from './hooks/useToggle/useToggle.js'; export * from './hooks/useUnmount/useUnmount.js'; -export * from './hooks/useWindowEventListener/useWindowEventListener.js'; export * from './utils/arrayRef/arrayRef.js'; export * from './utils/createTimeout/createTimeout.js'; +export * from './utils/isRefObject/isRefObject.js'; +export * from './utils/unref/unref.js'; diff --git a/src/utils/isRefObject/isRefObject.mdx b/src/utils/isRefObject/isRefObject.mdx new file mode 100644 index 0000000..219adcb --- /dev/null +++ b/src/utils/isRefObject/isRefObject.mdx @@ -0,0 +1,25 @@ +import { Meta } from '@storybook/blocks'; + + + +# isRefObject + +Checks if the target is a RefObject. + +## Reference + +```ts +function isRefObject(target: unknown): target is RefObject; +``` + +### Returns + +Boolean indicating if the target is a RefObject. + +## Usage + +```ts +isRefObject({ current: 'foo' }); // true +isRefObject('bar'); // false +isRefObject(null); // false +``` diff --git a/src/utils/isRefObject/isRefObject.test.ts b/src/utils/isRefObject/isRefObject.test.ts new file mode 100644 index 0000000..626da46 --- /dev/null +++ b/src/utils/isRefObject/isRefObject.test.ts @@ -0,0 +1,37 @@ +import { isRefObject } from './isRefObject.js'; + +describe('isRefObject', () => { + it('should return true if the value is a ref object with a truthy value', () => { + const value = { current: 'hello' }; + const result = isRefObject(value); + + expect(result).toBe(true); + }); + + it('should return true if the value is a ref object with a falsy value', () => { + const value = { current: false }; + const result = isRefObject(value); + + expect(result).toBe(true); + }); + + it('should return true if the value is a ref object with `null` value', () => { + const value = { current: null }; + const result = isRefObject(value); + + expect(result).toBe(true); + }); + + it('should return false if value is not a ref object', () => { + const value = 'world'; + const result = isRefObject(value); + + expect(result).toBe(false); + }); + + it('should return false if the input is null', () => { + const result = isRefObject(null); + + expect(result).toBe(false); + }); +}); diff --git a/src/utils/isRefObject/isRefObject.ts b/src/utils/isRefObject/isRefObject.ts new file mode 100644 index 0000000..f6ce9d5 --- /dev/null +++ b/src/utils/isRefObject/isRefObject.ts @@ -0,0 +1,8 @@ +import type { RefObject } from 'react'; + +/** + * Type guard to check if a value is a RefObject + */ +export function isRefObject(target: unknown): target is RefObject { + return target !== null && typeof target === 'object' && 'current' in target; +} diff --git a/src/utils/unref/unref.mdx b/src/utils/unref/unref.mdx new file mode 100644 index 0000000..c7936dd --- /dev/null +++ b/src/utils/unref/unref.mdx @@ -0,0 +1,53 @@ +import { Meta } from '@storybook/blocks'; + + + +# unref + +This module contains utilities for working with ref objects. + +## `Unreffable` + +Type for a value that is optionally wrapped in a RefObject type. This is useful for cases where a +value may be passed as either a plain value or a RefObject containing the value. + +### Example usage + +```ts +type MyType = Unreffable; + +const plainValue = 'foo' satisfies MyType; // plain value +const refObjectValue = { current: 'bar' } satisfies MyType; // RefObject containing value +``` + +## `Unref>` + +Type to get the value of an `Unreffable` object. If the input type is a `RefObject`, it returns +the type `T`. Otherwise, it returns the input type itself. + +### Example usage + +```ts +type MyPlainType = Unref; // string +type MyRefObject = Unref>; // string +``` + +## `unref(target: Unreffable): T | null` + +This function allows you to get the current value of a ref object, or the value as is. + +- `target` - The target value, which may be a RefObject or a plain value + +### Returns + +The current value of the ref object, or the input value if it is not a ref object. + +### Example usage + +```ts +const myRefObject = { current: 'foo' }; +unref(myRefObject); // 'foo' + +const myValue = 'bar'; +unref(myValue); // 'bar' +``` diff --git a/src/utils/unref/unref.test.ts b/src/utils/unref/unref.test.ts new file mode 100644 index 0000000..f6a389e --- /dev/null +++ b/src/utils/unref/unref.test.ts @@ -0,0 +1,23 @@ +import { unref } from './unref.js'; + +describe('unref', () => { + it('should return the current value of the ref object if it exists', () => { + const value = { current: 'hello' }; + const result = unref(value); + + expect(result).toEqual('hello'); + }); + + it('should return the original value if it is not a ref object', () => { + const value = 'world'; + const result = unref(value); + + expect(result).toEqual('world'); + }); + + it('should return null if the input is null', () => { + const result = unref(null); + + expect(result).toBeNull(); + }); +}); diff --git a/src/utils/unref/unref.ts b/src/utils/unref/unref.ts new file mode 100644 index 0000000..78784d6 --- /dev/null +++ b/src/utils/unref/unref.ts @@ -0,0 +1,19 @@ +import type { RefObject } from 'react'; +import { isRefObject } from '../isRefObject/isRefObject.js'; + +/** + * Type for a value that is optionally wrapped in a RefObject type + */ +export type Unreffable = RefObject | T; + +/** + * Type to get the value of a Unreffable + */ +export type Unref> = T extends RefObject ? U : T; + +/** + * This function returns the current value of a ref object, or the plain value. + */ +export function unref(target: Unreffable): T | null { + return isRefObject(target) ? target.current : target; +}