react-native-nitro-geolocation 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,44 +1,140 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
2
|
+
import type {
|
|
3
|
+
LocationError,
|
|
4
|
+
LocationRequestOptions
|
|
5
|
+
} from "../NitroGeolocation.nitro";
|
|
2
6
|
import { useGeolocationClient } from "../components/GeolocationProvider";
|
|
3
7
|
import type { GeolocationResponse } from "../types";
|
|
4
8
|
|
|
5
9
|
/**
|
|
6
|
-
*
|
|
7
|
-
|
|
10
|
+
* Options for useGetCurrentPosition hook (Query style).
|
|
11
|
+
*/
|
|
12
|
+
export interface UseGetCurrentPositionOptions extends LocationRequestOptions {
|
|
13
|
+
/**
|
|
14
|
+
* Whether to automatically fetch the current position.
|
|
15
|
+
* When false, only manual refetch() will trigger the request.
|
|
16
|
+
* @default true
|
|
17
|
+
*/
|
|
18
|
+
enabled?: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Hook for getting current location (one-time request) in Query style.
|
|
23
|
+
* Provides loading, error states, and data similar to TanStack Query.
|
|
8
24
|
*
|
|
9
|
-
* @
|
|
10
|
-
* @
|
|
25
|
+
* @param options - Location request options and enabled flag
|
|
26
|
+
* @returns Object containing position, loading/error states, and refetch function
|
|
11
27
|
*
|
|
12
28
|
* @example
|
|
13
29
|
* ```tsx
|
|
30
|
+
* // Auto-fetch on mount
|
|
14
31
|
* function MyComponent() {
|
|
15
|
-
* const {
|
|
32
|
+
* const {
|
|
33
|
+
* position,
|
|
34
|
+
* isLoading,
|
|
35
|
+
* isError,
|
|
36
|
+
* error,
|
|
37
|
+
* refetch
|
|
38
|
+
* } = useGetCurrentPosition({
|
|
39
|
+
* enabled: true,
|
|
40
|
+
* enableHighAccuracy: true,
|
|
41
|
+
* });
|
|
16
42
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* enableHighAccuracy: true,
|
|
21
|
-
* timeout: 15000,
|
|
22
|
-
* });
|
|
23
|
-
* console.log('Lat:', position.coords.latitude);
|
|
24
|
-
* console.log('Lng:', position.coords.longitude);
|
|
25
|
-
* } catch (error) {
|
|
26
|
-
* console.error('Location error:', error.message);
|
|
27
|
-
* }
|
|
28
|
-
* };
|
|
43
|
+
* if (isLoading) return <Text>Loading...</Text>;
|
|
44
|
+
* if (isError) return <Text>Error: {error?.message}</Text>;
|
|
45
|
+
* if (!position) return null;
|
|
29
46
|
*
|
|
30
|
-
* return
|
|
47
|
+
* return (
|
|
48
|
+
* <View>
|
|
49
|
+
* <Text>Lat: {position.coords.latitude}</Text>
|
|
50
|
+
* <Text>Lng: {position.coords.longitude}</Text>
|
|
51
|
+
* <Button title="Refresh" onPress={() => refetch()} />
|
|
52
|
+
* </View>
|
|
53
|
+
* );
|
|
54
|
+
* }
|
|
55
|
+
*
|
|
56
|
+
* // Manual trigger only
|
|
57
|
+
* function ManualComponent() {
|
|
58
|
+
* const { position, isLoading, refetch } = useGetCurrentPosition({
|
|
59
|
+
* enabled: false
|
|
60
|
+
* });
|
|
61
|
+
*
|
|
62
|
+
* return (
|
|
63
|
+
* <View>
|
|
64
|
+
* <Button
|
|
65
|
+
* title="Get Location"
|
|
66
|
+
* onPress={() => refetch()}
|
|
67
|
+
* disabled={isLoading}
|
|
68
|
+
* />
|
|
69
|
+
* {position && <Text>Lat: {position.coords.latitude}</Text>}
|
|
70
|
+
* </View>
|
|
71
|
+
* );
|
|
31
72
|
* }
|
|
32
73
|
* ```
|
|
33
74
|
*/
|
|
34
|
-
export function useGetCurrentPosition() {
|
|
75
|
+
export function useGetCurrentPosition(options?: UseGetCurrentPositionOptions) {
|
|
35
76
|
const client = useGeolocationClient();
|
|
36
77
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
78
|
+
const [position, setPosition] = useState<GeolocationResponse | null>(null);
|
|
79
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
80
|
+
const [isError, setIsError] = useState(false);
|
|
81
|
+
const [error, setError] = useState<LocationError | null>(null);
|
|
82
|
+
|
|
83
|
+
const isMountedRef = useRef(true);
|
|
84
|
+
const optionsRef = useRef(options);
|
|
85
|
+
|
|
86
|
+
// Update options ref when they change
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
optionsRef.current = options;
|
|
89
|
+
}, [options]);
|
|
90
|
+
|
|
91
|
+
const fetchPosition = useCallback(async () => {
|
|
92
|
+
if (!isMountedRef.current) return;
|
|
93
|
+
|
|
94
|
+
setIsLoading(true);
|
|
95
|
+
setIsError(false);
|
|
96
|
+
setError(null);
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
const result = await client.getCurrentPosition(optionsRef.current);
|
|
100
|
+
if (!isMountedRef.current) return;
|
|
101
|
+
|
|
102
|
+
setPosition(result);
|
|
103
|
+
setIsLoading(false);
|
|
104
|
+
} catch (err) {
|
|
105
|
+
if (!isMountedRef.current) return;
|
|
106
|
+
|
|
107
|
+
setIsError(true);
|
|
108
|
+
setError(err as LocationError);
|
|
109
|
+
setIsLoading(false);
|
|
110
|
+
}
|
|
111
|
+
}, [client]);
|
|
112
|
+
|
|
113
|
+
// Extract enabled flag for reactive dependency
|
|
114
|
+
const enabled = options?.enabled ?? true;
|
|
115
|
+
|
|
116
|
+
// Auto-fetch on mount if enabled
|
|
117
|
+
useEffect(() => {
|
|
118
|
+
if (enabled) {
|
|
119
|
+
fetchPosition();
|
|
42
120
|
}
|
|
121
|
+
// Only run when enabled changes, not when fetchPosition changes
|
|
122
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
123
|
+
}, [enabled]);
|
|
124
|
+
|
|
125
|
+
// Track mount status
|
|
126
|
+
useEffect(() => {
|
|
127
|
+
isMountedRef.current = true;
|
|
128
|
+
return () => {
|
|
129
|
+
isMountedRef.current = false;
|
|
130
|
+
};
|
|
131
|
+
}, []);
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
position,
|
|
135
|
+
isLoading,
|
|
136
|
+
isError,
|
|
137
|
+
error,
|
|
138
|
+
refetch: fetchPosition
|
|
43
139
|
};
|
|
44
140
|
}
|
|
@@ -1,38 +1,98 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
2
|
+
import type {
|
|
3
|
+
LocationError,
|
|
4
|
+
PermissionStatus
|
|
5
|
+
} from "../NitroGeolocation.nitro";
|
|
2
6
|
import { useGeolocationClient } from "../components/GeolocationProvider";
|
|
3
7
|
|
|
4
8
|
/**
|
|
5
|
-
* Hook
|
|
6
|
-
*
|
|
9
|
+
* Hook for requesting location permission in Mutation style.
|
|
10
|
+
* Provides pending, error states, and status similar to TanStack Query mutations.
|
|
7
11
|
*
|
|
8
|
-
* @returns Object containing requestPermission function
|
|
12
|
+
* @returns Object containing requestPermission function, status, and state flags
|
|
9
13
|
*
|
|
10
14
|
* @example
|
|
11
15
|
* ```tsx
|
|
12
16
|
* function MyComponent() {
|
|
13
|
-
* const {
|
|
17
|
+
* const {
|
|
18
|
+
* requestPermission,
|
|
19
|
+
* status,
|
|
20
|
+
* isPending,
|
|
21
|
+
* isError,
|
|
22
|
+
* error
|
|
23
|
+
* } = useRequestPermission();
|
|
14
24
|
*
|
|
15
25
|
* const handleRequest = async () => {
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* console.log('Permission granted!');
|
|
20
|
-
* }
|
|
21
|
-
* } catch (error) {
|
|
22
|
-
* console.error('Permission error:', error);
|
|
26
|
+
* const result = await requestPermission();
|
|
27
|
+
* if (result === 'granted') {
|
|
28
|
+
* console.log('Permission granted!');
|
|
23
29
|
* }
|
|
24
30
|
* };
|
|
25
31
|
*
|
|
26
|
-
* return
|
|
32
|
+
* return (
|
|
33
|
+
* <View>
|
|
34
|
+
* <Button
|
|
35
|
+
* onPress={handleRequest}
|
|
36
|
+
* disabled={isPending}
|
|
37
|
+
* >
|
|
38
|
+
* {isPending ? 'Requesting...' : 'Request Permission'}
|
|
39
|
+
* </Button>
|
|
40
|
+
* {isError && <Text>Error: {error?.message}</Text>}
|
|
41
|
+
* {status && <Text>Status: {status}</Text>}
|
|
42
|
+
* </View>
|
|
43
|
+
* );
|
|
27
44
|
* }
|
|
28
45
|
* ```
|
|
29
46
|
*/
|
|
30
47
|
export function useRequestPermission() {
|
|
31
48
|
const client = useGeolocationClient();
|
|
32
49
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
50
|
+
const [status, setStatus] = useState<PermissionStatus | null>(null);
|
|
51
|
+
const [isPending, setIsPending] = useState(false);
|
|
52
|
+
const [isError, setIsError] = useState(false);
|
|
53
|
+
const [error, setError] = useState<LocationError | null>(null);
|
|
54
|
+
|
|
55
|
+
const isMountedRef = useRef(true);
|
|
56
|
+
|
|
57
|
+
const requestPermission = useCallback(async (): Promise<PermissionStatus> => {
|
|
58
|
+
if (!isMountedRef.current) {
|
|
59
|
+
throw new Error("Component unmounted");
|
|
36
60
|
}
|
|
61
|
+
|
|
62
|
+
setIsPending(true);
|
|
63
|
+
setIsError(false);
|
|
64
|
+
setError(null);
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
const result = await client.requestPermission();
|
|
68
|
+
if (!isMountedRef.current) return result;
|
|
69
|
+
|
|
70
|
+
setStatus(result);
|
|
71
|
+
setIsPending(false);
|
|
72
|
+
return result;
|
|
73
|
+
} catch (err) {
|
|
74
|
+
if (!isMountedRef.current) throw err;
|
|
75
|
+
|
|
76
|
+
setIsError(true);
|
|
77
|
+
setError(err as LocationError);
|
|
78
|
+
setIsPending(false);
|
|
79
|
+
throw err;
|
|
80
|
+
}
|
|
81
|
+
}, [client]);
|
|
82
|
+
|
|
83
|
+
// Track mount status
|
|
84
|
+
useEffect(() => {
|
|
85
|
+
isMountedRef.current = true;
|
|
86
|
+
return () => {
|
|
87
|
+
isMountedRef.current = false;
|
|
88
|
+
};
|
|
89
|
+
}, []);
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
requestPermission,
|
|
93
|
+
status,
|
|
94
|
+
isPending,
|
|
95
|
+
isError,
|
|
96
|
+
error
|
|
37
97
|
};
|
|
38
98
|
}
|
|
@@ -26,12 +26,12 @@ export interface UseWatchPositionOptions extends LocationRequestOptions {
|
|
|
26
26
|
* Cleanup is automatic via useEffect.
|
|
27
27
|
*
|
|
28
28
|
* @param options - Location request options
|
|
29
|
-
* @returns Object containing current position
|
|
29
|
+
* @returns Object containing current position, error, and watching status
|
|
30
30
|
*
|
|
31
31
|
* @example
|
|
32
32
|
* ```tsx
|
|
33
33
|
* function LiveTracking() {
|
|
34
|
-
* const {
|
|
34
|
+
* const { position, error, isWatching } = useWatchPosition({
|
|
35
35
|
* enabled: true,
|
|
36
36
|
* enableHighAccuracy: true,
|
|
37
37
|
* distanceFilter: 10 // Update every 10 meters
|
|
@@ -39,12 +39,12 @@ export interface UseWatchPositionOptions extends LocationRequestOptions {
|
|
|
39
39
|
*
|
|
40
40
|
* if (!isWatching) return <Text>Not watching</Text>;
|
|
41
41
|
* if (error) return <Text>Error: {error.message}</Text>;
|
|
42
|
-
* if (!
|
|
42
|
+
* if (!position) return <Text>Waiting for location...</Text>;
|
|
43
43
|
*
|
|
44
44
|
* return (
|
|
45
45
|
* <Text>
|
|
46
|
-
* Current: {
|
|
47
|
-
* Accuracy: {
|
|
46
|
+
* Current: {position.coords.latitude}, {position.coords.longitude}
|
|
47
|
+
* Accuracy: {position.coords.accuracy}m
|
|
48
48
|
* </Text>
|
|
49
49
|
* );
|
|
50
50
|
* }
|
|
@@ -53,8 +53,9 @@ export interface UseWatchPositionOptions extends LocationRequestOptions {
|
|
|
53
53
|
export function useWatchPosition(options?: UseWatchPositionOptions) {
|
|
54
54
|
const client = useGeolocationClient();
|
|
55
55
|
|
|
56
|
-
const [
|
|
56
|
+
const [position, setPosition] = useState<GeolocationResponse | null>(null);
|
|
57
57
|
const [isWatching, setIsWatching] = useState(false);
|
|
58
|
+
const [error, setError] = useState<LocationError | null>(null);
|
|
58
59
|
|
|
59
60
|
// Store subscription token (hidden from user!)
|
|
60
61
|
const tokenRef = useRef<string | null>(null);
|
|
@@ -62,9 +63,18 @@ export function useWatchPosition(options?: UseWatchPositionOptions) {
|
|
|
62
63
|
// Track if component is mounted to prevent state updates after unmount
|
|
63
64
|
const isMountedRef = useRef(true);
|
|
64
65
|
|
|
66
|
+
// Store latest options in ref to avoid unnecessary re-subscriptions
|
|
67
|
+
const optionsRef = useRef(options);
|
|
68
|
+
|
|
69
|
+
// Update options ref whenever options change
|
|
65
70
|
useEffect(() => {
|
|
66
|
-
|
|
71
|
+
optionsRef.current = options;
|
|
72
|
+
}, [options]);
|
|
73
|
+
|
|
74
|
+
// Extract enabled flag for reactive dependency
|
|
75
|
+
const enabled = options?.enabled ?? false;
|
|
67
76
|
|
|
77
|
+
useEffect(() => {
|
|
68
78
|
if (!enabled) {
|
|
69
79
|
// Not enabled, ensure cleanup
|
|
70
80
|
if (tokenRef.current) {
|
|
@@ -75,21 +85,23 @@ export function useWatchPosition(options?: UseWatchPositionOptions) {
|
|
|
75
85
|
return;
|
|
76
86
|
}
|
|
77
87
|
|
|
78
|
-
// Start watching
|
|
88
|
+
// Start watching with latest options
|
|
79
89
|
setIsWatching(true);
|
|
90
|
+
setError(null);
|
|
80
91
|
|
|
81
92
|
const token = client.watchPosition(
|
|
82
|
-
(
|
|
93
|
+
(result: GeolocationResponse) => {
|
|
83
94
|
// Success callback
|
|
84
95
|
if (!isMountedRef.current) return;
|
|
85
|
-
|
|
96
|
+
setPosition(result);
|
|
97
|
+
setError(null);
|
|
86
98
|
},
|
|
87
99
|
(err: LocationError) => {
|
|
88
|
-
// Error callback
|
|
100
|
+
// Error callback
|
|
89
101
|
if (!isMountedRef.current) return;
|
|
90
|
-
|
|
102
|
+
setError(err);
|
|
91
103
|
},
|
|
92
|
-
|
|
104
|
+
optionsRef.current
|
|
93
105
|
);
|
|
94
106
|
|
|
95
107
|
tokenRef.current = token;
|
|
@@ -100,17 +112,7 @@ export function useWatchPosition(options?: UseWatchPositionOptions) {
|
|
|
100
112
|
client.unwatch(token);
|
|
101
113
|
}
|
|
102
114
|
};
|
|
103
|
-
}, [
|
|
104
|
-
client,
|
|
105
|
-
options?.enabled,
|
|
106
|
-
options?.enableHighAccuracy,
|
|
107
|
-
options?.distanceFilter,
|
|
108
|
-
options?.interval,
|
|
109
|
-
options?.fastestInterval,
|
|
110
|
-
options?.timeout,
|
|
111
|
-
options?.maximumAge,
|
|
112
|
-
options?.useSignificantChanges
|
|
113
|
-
]);
|
|
115
|
+
}, [enabled, client]); // Only re-subscribe when enabled/client changes
|
|
114
116
|
|
|
115
117
|
// Track mount status
|
|
116
118
|
useEffect(() => {
|
|
@@ -121,7 +123,8 @@ export function useWatchPosition(options?: UseWatchPositionOptions) {
|
|
|
121
123
|
}, []);
|
|
122
124
|
|
|
123
125
|
return {
|
|
124
|
-
|
|
126
|
+
position,
|
|
127
|
+
error,
|
|
125
128
|
isWatching
|
|
126
129
|
};
|
|
127
130
|
}
|