-
Notifications
You must be signed in to change notification settings - Fork 202
Expand file tree
/
Copy pathusePullToRefresh.ts
More file actions
93 lines (80 loc) · 2.54 KB
/
Copy pathusePullToRefresh.ts
File metadata and controls
93 lines (80 loc) · 2.54 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
import { useState, useCallback, useRef } from 'react';
interface UsePullToRefreshProps {
onRefresh: () => Promise<void>;
pullThreshold?: number;
maxPullDistance?: number;
disabled?: boolean;
}
export function usePullToRefresh({
onRefresh,
pullThreshold = 80,
maxPullDistance = 150,
disabled = false,
}: UsePullToRefreshProps) {
const [pullDistance, setPullDistance] = useState(0);
const [isRefreshing, setIsRefreshing] = useState(false);
const startYRef = useRef(0);
const currentYRef = useRef(0);
const isPullingRef = useRef(false);
const handleTouchStart = useCallback(
(e: React.TouchEvent | TouchEvent) => {
if (disabled || isRefreshing) return;
// Ensure we are at the top of the container/window before allowing pull
const scrollTop =
(e.currentTarget as HTMLElement).scrollTop ||
document.documentElement.scrollTop;
if (scrollTop > 0) return;
startYRef.current = e.touches[0].clientY;
currentYRef.current = startYRef.current;
isPullingRef.current = true;
},
[disabled, isRefreshing]
);
const handleTouchMove = useCallback(
(e: React.TouchEvent | TouchEvent) => {
if (!isPullingRef.current || disabled || isRefreshing) return;
const currentY = e.touches[0].clientY;
currentYRef.current = currentY;
const diff = currentY - startYRef.current;
// Only allow pulling down
if (diff > 0) {
// Prevent default to avoid browser's native pull-to-refresh
if (e.cancelable) {
e.preventDefault();
}
// Add some resistance
const resistance = diff * 0.5;
const newDistance = Math.min(resistance, maxPullDistance);
setPullDistance(newDistance);
} else {
setPullDistance(0);
}
},
[disabled, isRefreshing, maxPullDistance]
);
const handleTouchEnd = useCallback(async () => {
if (!isPullingRef.current || disabled || isRefreshing) return;
isPullingRef.current = false;
if (pullDistance >= pullThreshold) {
setIsRefreshing(true);
setPullDistance(pullThreshold); // keep it at threshold during refresh
try {
await onRefresh();
} finally {
setIsRefreshing(false);
setPullDistance(0);
}
} else {
setPullDistance(0);
}
}, [disabled, isRefreshing, onRefresh, pullDistance, pullThreshold]);
return {
pullDistance,
isRefreshing,
handlers: {
onTouchStart: handleTouchStart,
onTouchMove: handleTouchMove,
onTouchEnd: handleTouchEnd,
},
};
}