Skip to content

Commit d9a9472

Browse files
committed
feat: 🎸 Added new hooks
1 parent fec6220 commit d9a9472

11 files changed

+191
-9491
lines changed

README.md

Lines changed: 75 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,13 @@
1717
<img src="https://custom-icon-badges.demolab.com/badge/typescript-%23007ACC.svg?logo=typescript&logoColor=white" />
1818
</a>
1919
<a href="https://www.npmjs.com/package/@better-hooks/window">
20-
<img src="https://custom-icon-badges.demolab.com/bundlephobia/min/@better-hooks/window?color=64BC4B&logo=package" />
20+
<img src="https://custom-icon-badges.demolab.com/npm/v/@better-hooks/window.svg?logo=npm&color=E10098" />
2121
</a>
2222
<a href="https://www.npmjs.com/package/@better-hooks/window">
23-
<img src="https://custom-icon-badges.demolab.com/npm/v/@better-hooks/window.svg?logo=npm&color=E10098" />
23+
<img src="https://custom-icon-badges.demolab.com/bundlephobia/minzip/@better-hooks/window?color=blueviolet&logo=package" />
24+
</a>
25+
<a href="https://www.npmjs.com/package/@better-hooks/window">
26+
<img src="https://custom-icon-badges.demolab.com/npm/dm/@better-hooks/window?logoColor=fff&logo=trending-up" />
2427
</a>
2528
</p>
2629

@@ -34,10 +37,18 @@ Handle window events and observe window size
3437

3538
🚀 **Fast and light**
3639

37-
**Lifecycle window events**
40+
**Lifecycle Window events**
41+
42+
💎 **Lifecycle Document events**
3843

3944
🎯 **Window size**
4045

46+
🪄 **Window scroll position**
47+
48+
💡 **Window focus**
49+
50+
🎊 **SSR Support**
51+
4152
## Installation
4253

4354
```bash
@@ -62,6 +73,14 @@ import { useWindowEvent } from "@better-hooks/window";
6273

6374
const MyComponent: React.FC = () => {
6475
// Unmounts event with component lifecycle
76+
useWindowEvent("scroll", () => {
77+
// ... Do something
78+
});
79+
80+
useWindowEvent("wheel", () => {
81+
// ... Do something
82+
});
83+
6584
useWindowEvent("resize", () => {
6685
// ... Do something
6786
});
@@ -73,21 +92,21 @@ const MyComponent: React.FC = () => {
7392

7493
```
7594

95+
---
96+
97+
#### useDocumentEvent
98+
7699
```tsx
77100
import React from "react";
78-
import { useWindowEvent } from "@better-hooks/window";
101+
import { useDocumentEvent } from "@better-hooks/window";
79102

80103
const MyComponent: React.FC = () => {
81104
// Unmounts event with component lifecycle
82-
useWindowEvent("scroll", () => {
105+
useDocumentEvent("visibilitychange", () => {
83106
// ... Do something
84107
});
85108

86-
useWindowEvent("wheel", () => {
87-
// ... Do something
88-
});
89-
90-
useWindowEvent("resize", () => {
109+
useDocumentEvent("scroll", () => {
91110
// ... Do something
92111
});
93112

@@ -116,3 +135,49 @@ const MyComponent: React.FC = () => {
116135
}
117136

118137
```
138+
139+
---
140+
141+
#### useWindowScroll
142+
143+
```tsx
144+
import React from "react";
145+
import { useWindowScroll } from "@better-hooks/window";
146+
147+
const MyComponent: React.FC = () => {
148+
// Updates when scrolling
149+
const [x, y] = useWindowScroll()
150+
151+
return (
152+
// ...
153+
)
154+
}
155+
156+
```
157+
158+
---
159+
160+
#### useWindowFocus
161+
162+
```tsx
163+
import React from "react";
164+
import { useWindowFocus } from "@better-hooks/window";
165+
166+
const MyComponent: React.FC = () => {
167+
// Updates when user leave our page
168+
const focus = useWindowFocus()
169+
170+
useEffect(() => {
171+
if(focus) {
172+
// User is using our page
173+
} else {
174+
// User has minimized window or leaved our page to different tab
175+
}
176+
}, [focus])
177+
178+
return (
179+
// ...
180+
)
181+
}
182+
183+
```

package.json

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -127,8 +127,5 @@
127127
"useDidUnmount",
128128
"useDidRender",
129129
"useWillUnmount"
130-
],
131-
"dependencies": {
132-
"@better-typed/react-lifecycle-hooks": "^1.0.5"
133-
}
130+
]
134131
}

src/hooks/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,5 @@
1+
export * from "./use-document-event.hook";
12
export * from "./use-window-event.hook";
3+
export * from "./use-window-focus.hook";
4+
export * from "./use-window-scroll.hook";
25
export * from "./use-window-size.hook";
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/* eslint-disable react-hooks/exhaustive-deps */
2+
3+
import { isBrowser } from "utils";
4+
import { useIsomorphicEffect } from "./use-isomorphic-effect.hook";
5+
6+
type EventHandler<T extends Event = Event> = (e: T) => void;
7+
8+
type DocumentEventHook = {
9+
<K extends keyof DocumentEventMap>(
10+
event: K | [K, AddEventListenerOptions],
11+
handler: EventHandler<DocumentEventMap[K]>,
12+
dependencies?: any[],
13+
): void;
14+
};
15+
16+
const unpackValue = <K extends keyof DocumentEventMap>(
17+
event: K | [K, AddEventListenerOptions],
18+
): [K, AddEventListenerOptions] => {
19+
if (typeof event === "string") {
20+
return [event, {}];
21+
}
22+
return event;
23+
};
24+
25+
export const useDocumentEvent: DocumentEventHook = (event, handler, dependencies = []) => {
26+
useIsomorphicEffect(() => {
27+
if (!isBrowser) return;
28+
29+
const [name, options] = unpackValue(event);
30+
const documentOptions = typeof options === "object" ? options : {};
31+
32+
document.addEventListener(name, handler, documentOptions);
33+
return () => {
34+
document.removeEventListener(name, handler, documentOptions);
35+
};
36+
}, [JSON.stringify(event), ...dependencies]);
37+
};
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/* eslint-disable react-hooks/exhaustive-deps */
2+
import { useEffect, useLayoutEffect } from "react";
3+
4+
import { isBrowser } from "utils";
5+
6+
const effect = isBrowser ? useEffect : useLayoutEffect;
7+
8+
export const useIsomorphicEffect = effect;

src/hooks/use-window-event.hook.ts

Lines changed: 18 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,37 @@
1-
import { useRef } from "react";
2-
import { useDidUpdate, useWillUnmount } from "@better-typed/react-lifecycle-hooks";
1+
/* eslint-disable react-hooks/exhaustive-deps */
32

4-
import { getIsClient } from "utils";
3+
import { isBrowser } from "utils";
4+
import { useIsomorphicEffect } from "./use-isomorphic-effect.hook";
55

66
type EventHandler<T extends Event = Event> = (e: T) => void;
77

88
type WindowEventHook = {
99
<K extends keyof WindowEventMap>(
10-
value: K | [K, AddEventListenerOptions],
10+
event: K | [K, AddEventListenerOptions],
1111
handler: EventHandler<WindowEventMap[K]>,
1212
dependencies?: any[],
1313
): void;
1414
};
1515

1616
const unpackValue = <K extends keyof WindowEventMap>(
17-
value: K | [K, AddEventListenerOptions],
17+
event: K | [K, AddEventListenerOptions],
1818
): [K, AddEventListenerOptions] => {
19-
if (typeof value === "string") {
20-
return [value, {}];
19+
if (typeof event === "string") {
20+
return [event, {}];
2121
}
22-
return value;
22+
return event;
2323
};
2424

25-
export const useWindowEvent: WindowEventHook = (value, handler, dependencies = []) => {
26-
const didUnmount = useRef(false);
27-
useWillUnmount(() => {
28-
didUnmount.current = true;
29-
});
25+
export const useWindowEvent: WindowEventHook = (event, handler, dependencies = []) => {
26+
useIsomorphicEffect(() => {
27+
if (!isBrowser) return;
3028

31-
useDidUpdate(
32-
() => {
33-
const isClient = getIsClient();
34-
if (!isClient) return;
29+
const [name, options] = unpackValue(event);
30+
const windowOptions = typeof options === "object" ? options : {};
3531

36-
const [name, options] = unpackValue(value);
37-
const windowOptions = typeof options === "object" ? options : {};
38-
39-
window.addEventListener(name, handler, windowOptions);
40-
return () => window.removeEventListener(name, handler, windowOptions);
41-
},
42-
[JSON.stringify(value), ...dependencies],
43-
true,
44-
);
32+
window.addEventListener(name, handler, windowOptions);
33+
return () => {
34+
window.removeEventListener(name, handler, windowOptions);
35+
};
36+
}, [JSON.stringify(event), ...dependencies]);
4537
};

src/hooks/use-window-focus.hook.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { useState } from "react";
2+
3+
import { isBrowser } from "utils";
4+
import { useDocumentEvent } from "./use-document-event.hook";
5+
import { useWindowEvent } from "./use-window-event.hook";
6+
7+
export type FocusType = boolean;
8+
9+
const getState = () => (isBrowser ? document.visibilityState === "visible" : false);
10+
11+
export const useWindowFocus = (): FocusType => {
12+
const [focus, setFocus] = useState(getState);
13+
14+
useDocumentEvent("visibilitychange", () => setFocus(true));
15+
useWindowEvent("focus", () => setFocus(true));
16+
useWindowEvent("blur", () => setFocus(false));
17+
18+
return focus;
19+
};
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { useState } from "react";
2+
3+
import { isBrowser } from "utils";
4+
import { useWindowEvent } from "./use-window-event.hook";
5+
6+
export type ScrollXType = number;
7+
export type ScrollYType = number;
8+
export type UseWindowScrollType = [ScrollXType, ScrollYType];
9+
10+
const getPosition = () => [isBrowser ? window.screenX : 0, isBrowser ? window.scrollY : 0];
11+
12+
export const useWindowScroll = () => {
13+
const [values, setValues] = useState(getPosition);
14+
15+
useWindowEvent(["scroll", { capture: false, passive: true }], () => {
16+
setValues(getPosition);
17+
});
18+
19+
return values;
20+
};

src/hooks/use-window-size.hook.ts

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,24 @@
11
import { useState } from "react";
22

3-
import { getIsClient } from "utils";
3+
import { isBrowser } from "utils";
44
import { useWindowEvent } from "./use-window-event.hook";
55

66
export type WindowWidthType = number;
77
export type WindowHeightType = number;
8-
98
export type UseWindowSizeType = [WindowWidthType, WindowHeightType];
109

11-
export const useWindowSize = (onResize?: (size: UseWindowSizeType) => void) => {
12-
const isClient = getIsClient();
13-
14-
const getSize = (): UseWindowSizeType => [
15-
isClient ? window.innerWidth : 0,
16-
isClient ? window.innerHeight : 0,
17-
];
10+
const getSize = (): UseWindowSizeType => [
11+
isBrowser ? window.innerWidth : 0,
12+
isBrowser ? window.innerHeight : 0,
13+
];
1814

15+
export const useWindowSize = (onResize?: (size: UseWindowSizeType) => void) => {
1916
const [windowSize, setWindowSize] = useState<UseWindowSizeType>(getSize);
2017

2118
const handleResize = () => {
22-
setWindowSize(getSize());
23-
onResize?.(getSize());
19+
const size = getSize();
20+
setWindowSize(size);
21+
onResize?.(size);
2422
};
2523

2624
useWindowEvent("resize", handleResize);

src/utils/window.utils.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1 @@
1-
export const getIsClient = () => {
2-
try {
3-
return !!window.addEventListener;
4-
} catch (err) {
5-
return false;
6-
}
7-
};
1+
export const isBrowser = typeof window !== "undefined";

0 commit comments

Comments
 (0)