Skip to content

Commit

Permalink
Update event listener hooks (#118)
Browse files Browse the repository at this point in the history
* 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: #118 (comment)

* Rename getRefObjectOption to unref

* Remove unnecessary useDocument and useWindow hooks

* Update src/utils/unref/unref.mdx

Co-authored-by: Arjan van Wijk <[email protected]>

---------

Co-authored-by: Arjan van Wijk <[email protected]>
  • Loading branch information
leroykorterink and ThaNarie authored Apr 4, 2023
1 parent eae00dc commit 09fe1e8
Show file tree
Hide file tree
Showing 24 changed files with 267 additions and 1,901 deletions.

This file was deleted.

This file was deleted.

This file was deleted.

12 changes: 0 additions & 12 deletions src/hooks/useDocumentEventListener/useDocumentEventListener.ts

This file was deleted.

30 changes: 22 additions & 8 deletions src/hooks/useEventListener/useEventListener.stories.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,42 @@ removed when the component unmounts.
## Reference

```ts
export function useEventListener<T extends EventTarget, K extends keyof EventMap>(
target: T | undefined,
event: K,
listener: (this: EventTarget, event: EventMap[K]) => void,
options?: boolean | EventListenerOptions,
function useEventListener<T extends EventTarget>(
targetOption: RefObject<T> | 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<HTMLDivElement>(null);

useEventListener(ref, 'click', (event) => {
...
});

return <div ref={ref}>...</div>;
}
```

### 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) => {
...
});
```
Expand Down
4 changes: 2 additions & 2 deletions src/hooks/useEventListener/useEventListener.stories.tsx
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -10,7 +10,7 @@ export default {
function DemoComponent(): ReactElement {
const [text, setText] = useState<ReadonlyArray<string>>([]);

useEventListener(typeof document === 'undefined' ? undefined : document, 'focusin', (event) => {
useEventListener(globalThis.document, 'focusin', (event) => {
// eslint-disable-next-line no-console
setText((previous) => [
...previous,
Expand Down
56 changes: 43 additions & 13 deletions src/hooks/useEventListener/useEventListener.test.tsx
Original file line number Diff line number Diff line change
@@ -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<HTMLDivElement>();

function Test(): ReactElement {
useEventListener(ref, 'click', spy);

return <div ref={ref} />;
}

render(<Test />);

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<HTMLDivElement | null>(null);

useEffect(() => {
exposedRef = ref;
}, [ref]);

useEventListener(ref, 'click', spy);

return <div ref={setRef} />;
}

render(<Test />);

// @ts-expect-error typescript doesn't infer type for exposedRef correctly
exposedRef?.click();

expect(spy).toBeCalledTimes(1);
});
});
Loading

0 comments on commit 09fe1e8

Please sign in to comment.