Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add EventTarget support to useEventListener #585

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/usehooks-ts/src/useEventListener/useEventListener.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ Find which kind of Event you want to extends:
- `HTMLElementEventMap`
- `DocumentEventMap`

This hook also supports any implementation of the `EventTarget` interface, however this doesn't bring the same level of type-safety that the `*EventMap`s do.

Then declare your custom event:

```ts
Expand Down
33 changes: 33 additions & 0 deletions packages/usehooks-ts/src/useEventListener/useEventListener.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@ const docRemoveEventListenerSpy = vitest.spyOn(
'removeEventListener',
)

class TestTarget extends EventTarget {}
const testTarget = new TestTarget()
const targetAddEventListenerSpy = vitest.spyOn(testTarget, 'addEventListener')
const targetRemoveEventListenerSpy = vitest.spyOn(
testTarget,
'removeEventListener',
)

describe('useEventListener()', () => {
afterEach(() => {
vitest.clearAllMocks()
Expand Down Expand Up @@ -92,6 +100,31 @@ describe('useEventListener()', () => {
)
})

it('should bind/unbind the event listener to the EventTarget when EventTarget is provided', () => {
const eventName = 'test-event'
const handler = vitest.fn()
const options = undefined

const { unmount } = renderHook(() => {
useEventListener(eventName, handler, testTarget, options)
})

expect(targetAddEventListenerSpy).toHaveBeenCalledTimes(1)
expect(targetAddEventListenerSpy).toHaveBeenCalledWith(
eventName,
expect.any(Function),
options,
)

unmount()

expect(targetRemoveEventListenerSpy).toHaveBeenCalledWith(
eventName,
expect.any(Function),
options,
)
})

it('should bind/unbind the event listener to the document when document is provided', () => {
const eventName = 'test-event'
const handler = vitest.fn()
Expand Down
19 changes: 17 additions & 2 deletions packages/usehooks-ts/src/useEventListener/useEventListener.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ import type { RefObject } from 'react'

import { useIsomorphicLayoutEffect } from '../useIsomorphicLayoutEffect/useIsomorphicLayoutEffect'

// EventTarget-based useEventListener interface
function useEventListener<K extends string>(
eventName: K,
handler: (event: Event) => void,
element: EventTarget,
options?: boolean | AddEventListenerOptions,
): void

// MediaQueryList Event based useEventListener interface
function useEventListener<K extends keyof MediaQueryListEventMap>(
eventName: K,
Expand Down Expand Up @@ -88,7 +96,7 @@ function useEventListener<
| MediaQueryListEventMap[KM]
| Event,
) => void,
element?: RefObject<T>,
element?: RefObject<T> | EventTarget,
options?: boolean | AddEventListenerOptions,
) {
// Create a ref that stores handler
Expand All @@ -100,7 +108,14 @@ function useEventListener<

useEffect(() => {
// Define the listening target
const targetElement: T | Window = element?.current ?? window
let targetElement: T | Window | EventTarget
if (element === undefined) {
targetElement = window
} else if ('current' in element) {
targetElement = element.current ?? window
} else {
targetElement = element
}

if (!(targetElement && targetElement.addEventListener)) return

Expand Down