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,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-nitro-geolocation",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "⚡🚀Blazing-fast geolocation for React Native powered by Nitro Modules",
5
5
  "main": "src/index",
6
6
  "source": "src/index",
@@ -1,44 +1,140 @@
1
- import type { LocationRequestOptions } from "../NitroGeolocation.nitro";
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
- * Hook that returns a function to get current location (one-time request).
7
- * The returned function throws error if permission denied, timeout, or unavailable.
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
- * @returns Object containing getCurrentPosition function
10
- * @throws LocationError with code and message
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 { getCurrentPosition } = useGetCurrentPosition();
32
+ * const {
33
+ * position,
34
+ * isLoading,
35
+ * isError,
36
+ * error,
37
+ * refetch
38
+ * } = useGetCurrentPosition({
39
+ * enabled: true,
40
+ * enableHighAccuracy: true,
41
+ * });
16
42
  *
17
- * const handleGetLocation = async () => {
18
- * try {
19
- * const position = await getCurrentPosition({
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 <Button onPress={handleGetLocation} />;
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
- return {
38
- getCurrentPosition: (
39
- options?: LocationRequestOptions
40
- ): Promise<GeolocationResponse> => {
41
- return client.getCurrentPosition(options);
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 type { PermissionStatus } from "../NitroGeolocation.nitro";
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 that returns a function to request location permission from the user.
6
- * Shows system permission dialog if not yet determined.
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 { requestPermission } = useRequestPermission();
17
+ * const {
18
+ * requestPermission,
19
+ * status,
20
+ * isPending,
21
+ * isError,
22
+ * error
23
+ * } = useRequestPermission();
14
24
  *
15
25
  * const handleRequest = async () => {
16
- * try {
17
- * const status = await requestPermission();
18
- * if (status === 'granted') {
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 <Button onPress={handleRequest} />;
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
- return {
34
- requestPermission: (): Promise<PermissionStatus> => {
35
- return client.requestPermission();
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 data, error, and watching status
29
+ * @returns Object containing current position, error, and watching status
30
30
  *
31
31
  * @example
32
32
  * ```tsx
33
33
  * function LiveTracking() {
34
- * const { data, error, isWatching } = useWatchPosition({
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 (!data) return <Text>Waiting for location...</Text>;
42
+ * if (!position) return <Text>Waiting for location...</Text>;
43
43
  *
44
44
  * return (
45
45
  * <Text>
46
- * Current: {data.coords.latitude}, {data.coords.longitude}
47
- * Accuracy: {data.coords.accuracy}m
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 [data, setData] = useState<GeolocationResponse | null>(null);
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
- const enabled = options?.enabled ?? false;
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
- (position: GeolocationResponse) => {
93
+ (result: GeolocationResponse) => {
83
94
  // Success callback
84
95
  if (!isMountedRef.current) return;
85
- setData(position);
96
+ setPosition(result);
97
+ setError(null);
86
98
  },
87
99
  (err: LocationError) => {
88
- // Error callback - throw to be caught by Error Boundary
100
+ // Error callback
89
101
  if (!isMountedRef.current) return;
90
- throw new Error(err.message);
102
+ setError(err);
91
103
  },
92
- options
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
- data,
126
+ position,
127
+ error,
125
128
  isWatching
126
129
  };
127
130
  }