Skip to content

Commit 90257d0

Browse files
committed
Merge main and updates
2 parents 735a278 + 627b66f commit 90257d0

File tree

12 files changed

+400
-78
lines changed

12 files changed

+400
-78
lines changed

README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
# useHooks
44

5-
A collection of modern, server-safe React hooks – from the ui.dev team
5+
A collection of modern, server-safe React hooks – from the [ui.dev](https://ui.dev) team.
6+
7+
Compatible with React v18.0.0+.
68

79
## Standard
810

@@ -13,12 +15,12 @@ A collection of modern, server-safe React hooks – from the ui.dev team
1315
### Hooks
1416

1517
- [useBattery](https://usehooks.com/usebattery)
16-
- [useClickAway](https://usehooks.com/useclickAway)
18+
- [useClickAway](https://usehooks.com/useclickaway)
1719
- [useCopyToClipboard](https://usehooks.com/usecopytoclipboard)
1820
- [useCounter](https://usehooks.com/usecounter)
1921
- [useDebounce](https://usehooks.com/usedebounce)
2022
- [useDefault](https://usehooks.com/usedefault)
21-
- [useDocumentTitle](https://usehooks.com/usedocumentTitle)
23+
- [useDocumentTitle](https://usehooks.com/usedocumenttitle)
2224
- [useFavicon](https://usehooks.com/usefavicon)
2325
- [useGeolocation](https://usehooks.com/usegeolocation)
2426
- [useHistoryState](https://usehooks.com/usehistoryState)

index.d.ts

Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
import * as React from "react";
2+
3+
export type BatteryManager = {
4+
supported: boolean;
5+
loading: boolean;
6+
level: number | null;
7+
charging: boolean | null;
8+
chargingTime: number | null;
9+
dischargingTime: number | null;
10+
};
11+
12+
export type GeolocationState = {
13+
loading: boolean;
14+
accuracy: number | null;
15+
altitude: number | null;
16+
altitudeAccuracy: number | null;
17+
heading: number | null;
18+
latitude: number | null;
19+
longitude: number | null;
20+
speed: number | null;
21+
timestamp: number | null;
22+
error: GeolocationPositionError | null;
23+
};
24+
25+
export type HistoryState<T> = {
26+
state: T;
27+
set: (newPresent: T) => void;
28+
undo: () => void;
29+
redo: () => void;
30+
clear: () => void;
31+
canUndo: boolean;
32+
canRedo: boolean;
33+
};
34+
35+
export type LongPressOptions = {
36+
threshold?: number;
37+
onStart?: (e: Event) => void;
38+
onFinish?: (e: Event) => void;
39+
onCancel?: (e: Event) => void;
40+
};
41+
42+
export type LongPressFns = {
43+
onMouseDown: (e: React.MouseEvent) => void;
44+
onMouseUp: (e: React.MouseEvent) => void;
45+
onMouseLeave: (e: React.MouseEvent) => void;
46+
onTouchStart: (e: React.TouchEvent) => void;
47+
onTouchEnd: (e: React.TouchEvent) => void;
48+
};
49+
50+
export type MousePosition = {
51+
x: number;
52+
y: number;
53+
elementX: number;
54+
elementY: number;
55+
elementPositionX: number;
56+
elementPositionY: number;
57+
};
58+
59+
export type NetworkState = {
60+
online: boolean;
61+
downlink: number | null;
62+
downlinkMax: number | null;
63+
effectiveType: string | null;
64+
rtt: number | null;
65+
saveData: boolean | null;
66+
type: string | null;
67+
};
68+
69+
export type CustomList<T> = {
70+
set: (l: T[]) => void;
71+
push: (element: T) => void;
72+
removeAt: (index: number) => void;
73+
insertAt: (index: number, element: T) => void;
74+
updateAt: (index: number, element: T) => void;
75+
clear: () => void;
76+
};
77+
78+
export type CustomQueue<T> = {
79+
add: (element: T) => void;
80+
remove: () => T | undefined;
81+
clear: () => void;
82+
first: T | undefined;
83+
last: T | undefined;
84+
size: number;
85+
};
86+
87+
export type RenderInfo = {
88+
name: string;
89+
renders: number;
90+
sinceLastRender: number;
91+
timestamp: number;
92+
};
93+
94+
export type SpeechOptions = {
95+
lang?: string;
96+
voice?: {
97+
lang?: string;
98+
name?: string;
99+
};
100+
rate?: number;
101+
pitch?: number;
102+
volume?: number;
103+
};
104+
105+
export type SpeechState = {
106+
isPlaying: boolean;
107+
status: "init" | "play" | "pause" | "stop";
108+
lang: string;
109+
voiceInfo: {
110+
lang: string;
111+
name: string;
112+
};
113+
rate: number;
114+
pitch: number;
115+
volume: number;
116+
};
117+
118+
declare module "@uidotdev/usehooks" {
119+
export function useBattery(): BatteryManager;
120+
121+
export function useClickAway<T extends Element>(
122+
cb: (e: Event) => void
123+
): React.MutableRefObject<T>;
124+
125+
export function useCopyToClipboard(): [
126+
{
127+
error: Error | null;
128+
text: string | null;
129+
},
130+
(value: string) => Promise<void>
131+
];
132+
133+
export function useCounter(
134+
startingValue?: number,
135+
options?: {
136+
min?: number;
137+
max?: number;
138+
}
139+
): [
140+
number,
141+
{
142+
increment: () => void;
143+
decrement: () => void;
144+
set: (nextCount: number) => void;
145+
reset: () => void;
146+
}
147+
];
148+
149+
export function useDebounce<T>(value: T, delay: number): T;
150+
151+
export function useDefault<T>(
152+
initialValue: T,
153+
defaultValue: T
154+
): [T, React.Dispatch<React.SetStateAction<T>>];
155+
156+
export function useDocumentTitle(title: string): void;
157+
158+
export function useFavicon(url: string): void;
159+
160+
export function useGeolocation(options?: PositionOptions): GeolocationState;
161+
162+
export function useHistoryState<T>(initialPresent?: T): HistoryState<T>;
163+
164+
export function useHover<T extends Element>(): [
165+
React.MutableRefObject<T>,
166+
boolean
167+
];
168+
169+
export function useIdle(ms?: number): boolean;
170+
171+
export function useIntersectionObserver(
172+
options?: IntersectionObserverInit
173+
): [React.MutableRefObject<Element>, IntersectionObserverEntry | null];
174+
175+
export function useIsClient(): boolean;
176+
177+
export function useIsFirstRender(): boolean;
178+
179+
export function useList<T>(defaultList?: T[]): [T[], CustomList<T>];
180+
181+
export function useLockBodyScroll(): void;
182+
183+
export function useLongPress(
184+
callback: (e: Event) => void,
185+
options?: LongPressOptions
186+
): LongPressFns;
187+
188+
export function useMap<T>(initialState?: T): Map<T, any>;
189+
190+
export function useMeasure<T extends Element>(): [
191+
React.MutableRefObject<T>,
192+
{
193+
width: number | null;
194+
height: number | null;
195+
}
196+
];
197+
198+
export function useMediaQuery(query: string): boolean;
199+
200+
export function useMouse<T extends Element>(): [
201+
MousePosition,
202+
React.MutableRefObject<T>
203+
];
204+
205+
export function useNetworkState(): NetworkState;
206+
207+
export function useObjectState<T>(initialValue: T): [T, (arg: T) => void];
208+
209+
export function useOrientation(): {
210+
angle: number;
211+
type: string;
212+
};
213+
214+
export function usePreferredLanguage(): string;
215+
216+
export function usePrevious<T>(newValue: T): T;
217+
218+
export function useQueue<T>(initialValue?: T[]): CustomQueue<T>;
219+
220+
export function useRenderCount(): number;
221+
222+
export function useRenderInfo(name?: string): RenderInfo | undefined;
223+
224+
export function useScript(
225+
src: string,
226+
options?: {
227+
removeOnUnmount?: boolean;
228+
}
229+
): "idle" | "loading" | "ready" | "error";
230+
231+
export function useSet<T>(values?: T[]): Set<T>;
232+
233+
export function useSpeech(text: string, options?: SpeechOptions): SpeechState;
234+
235+
export function useThrottle<T>(value: T, delay: number): T;
236+
237+
export function useToggle(
238+
initialValue?: boolean
239+
): [boolean, (newValue?: boolean) => void];
240+
241+
export function useVisibilityChange(): boolean;
242+
243+
export function useWindowScroll(): [
244+
{
245+
x: number | null;
246+
y: number | null;
247+
},
248+
(args: unknown) => void
249+
];
250+
251+
export function useWindowSize(): {
252+
width: number | null;
253+
height: number | null;
254+
};
255+
}

index.js

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,10 @@ export function useClickAway(cb) {
109109
const ref = React.useRef(null);
110110
const refCb = React.useRef(cb);
111111

112+
React.useLayoutEffect(() => {
113+
refCb.current = cb;
114+
});
115+
112116
React.useEffect(() => {
113117
const handler = (e) => {
114118
const element = ref.current;
@@ -163,20 +167,27 @@ export function useCopyToClipboard() {
163167
return [state, copyToClipboard];
164168
}
165169

166-
export function useContinuousRetry(callback, interval = 100) {
170+
export function useContinuousRetry(callback, interval = 100, options = {}) {
171+
const { maxRetries = Infinity } = options;
167172
const [hasResolved, setHasResolved] = React.useState(false);
168173
const onInterval = React.useEffectEvent(callback);
169174

170175
React.useEffect(() => {
176+
let retries = 0;
177+
171178
const id = window.setInterval(() => {
172179
if (onInterval()) {
173180
setHasResolved(true);
174181
window.clearInterval(id);
182+
} else if (retries >= maxRetries) {
183+
window.clearInterval(id);
184+
} else {
185+
retries += 1;
175186
}
176187
}, interval);
177188

178189
return () => window.clearInterval(id);
179-
}, [interval]);
190+
}, [interval, maxRetries]);
180191

181192
return hasResolved;
182193
}
@@ -820,11 +831,7 @@ export function useLocalStorage(key, initialValue) {
820831
[key, localState]
821832
);
822833

823-
const onStorageChange = React.useEffectEvent((event) => {
824-
if (event?.key && event.key !== key) {
825-
return;
826-
}
827-
834+
const onStorageChange = React.useEffectEvent(() => {
828835
setLocalState(readValue());
829836
});
830837

@@ -875,7 +882,6 @@ export function useLogger(name, ...rest) {
875882
}, []);
876883
}
877884

878-
879885
export function useLongPress(
880886
callback,
881887
{ threshold = 400, onStart, onFinish, onCancel } = {}
@@ -885,6 +891,10 @@ export function useLongPress(
885891
const timerId = React.useRef();
886892
const cbRef = React.useRef(callback);
887893

894+
React.useLayoutEffect(() => {
895+
cbRef.current = callback;
896+
});
897+
888898
const start = React.useCallback(
889899
() => (event) => {
890900
if (isPressed.current) return;
@@ -1080,8 +1090,6 @@ export function useMouse() {
10801090
return [state, ref];
10811091
}
10821092

1083-
1084-
10851093
export function useNetworkState() {
10861094
const connection =
10871095
navigator?.connection ||
@@ -1313,6 +1321,7 @@ export function useQueue(initialValue = []) {
13131321
first: queue[0],
13141322
last: queue[queue.length - 1],
13151323
size: queue.length,
1324+
queue,
13161325
};
13171326
}
13181327

@@ -1383,11 +1392,7 @@ export function useSessionStorage(key, initialValue) {
13831392
[key, localState]
13841393
);
13851394

1386-
const onStorageChange = React.useEffectEvent((event) => {
1387-
if (event?.key && event.key !== key) {
1388-
return;
1389-
}
1390-
1395+
const onStorageChange = React.useEffectEvent(() => {
13911396
setLocalState(readValue());
13921397
});
13931398

@@ -1615,6 +1620,7 @@ export function useVisibilityChange() {
16151620
setDocumentVisibility(true);
16161621
}
16171622
};
1623+
handleChange();
16181624

16191625
document.addEventListener("visibilitychange", handleChange);
16201626

0 commit comments

Comments
 (0)