@ttoss/google-maps 2.0.5 → 2.0.7
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/README.md +49 -0
- package/dist/esm/index.js +49 -14
- package/dist/index.d.ts +9 -1
- package/package.json +5 -6
- package/src/GoogleMapsProvider.tsx +0 -95
- package/src/index.ts +0 -5
- package/src/typings.d.ts +0 -1
- package/src/useGeocoder.ts +0 -21
- package/src/useGoogleMaps.ts +0 -20
- package/src/useMap.ts +0 -56
- package/src/usePlacesAutocomplete/debounce.ts +0 -20
- package/src/usePlacesAutocomplete/index.ts +0 -208
- package/src/usePlacesAutocomplete/useLatest.ts +0 -7
package/README.md
CHANGED
|
@@ -82,3 +82,52 @@ const MyComponent = () => {
|
|
|
82
82
|
```
|
|
83
83
|
|
|
84
84
|
With this, you can perform any operation that the `google.maps` object allows, such as creating markers, drawing polygons, etc.
|
|
85
|
+
|
|
86
|
+
### Using with Next.js (custom Script component)
|
|
87
|
+
|
|
88
|
+
If you use Next.js, you can use the `GoogleMapsProvider` passing [Next.js `Script`](https://nextjs.org/docs/app/api-reference/components/script) component as a prop:
|
|
89
|
+
|
|
90
|
+
```tsx
|
|
91
|
+
import { GoogleMapsProvider } from '@ttoss/google-maps';
|
|
92
|
+
import Script from 'next/script';
|
|
93
|
+
|
|
94
|
+
const App = ({ children }) => {
|
|
95
|
+
return (
|
|
96
|
+
<OtherProviders>
|
|
97
|
+
<GoogleMapsProvider
|
|
98
|
+
apiKey={process.env.GOOGLE_MAPS_API_KEY}
|
|
99
|
+
Script={Script}
|
|
100
|
+
>
|
|
101
|
+
{children}
|
|
102
|
+
</GoogleMapsProvider>
|
|
103
|
+
</OtherProviders>
|
|
104
|
+
);
|
|
105
|
+
};
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## API
|
|
109
|
+
|
|
110
|
+
### `GoogleMapsProvider`
|
|
111
|
+
|
|
112
|
+
#### Props
|
|
113
|
+
|
|
114
|
+
- `apiKey`: string - Google Maps API key.
|
|
115
|
+
- `libraries`: string[] - [Libraries to load](https://developers.google.com/maps/documentation/javascript/libraries).
|
|
116
|
+
- `language`: string - [Language](https://developers.google.com/maps/documentation/javascript/localization).
|
|
117
|
+
- `Script`: React.ComponentType - Custom `Script` component to use.
|
|
118
|
+
- `onError`: (error: Error) => void - Callback to handle script loading errors.
|
|
119
|
+
|
|
120
|
+
### `useMap`
|
|
121
|
+
|
|
122
|
+
#### Returns
|
|
123
|
+
|
|
124
|
+
- `ref`: React.RefObject<HTMLDivElement> - Reference to the map container.
|
|
125
|
+
- `map`: google.maps.Map | null - Google Maps object.
|
|
126
|
+
|
|
127
|
+
### `useGoogleMaps`
|
|
128
|
+
|
|
129
|
+
#### Returns
|
|
130
|
+
|
|
131
|
+
- `google`: typeof google - `google.maps` object.
|
|
132
|
+
- `status`: 'idle' | 'error' | 'loading' | 'ready' - Status of the script loading.
|
|
133
|
+
- `isReady`: boolean - Whether the script is ready. The same as `status === 'ready'`.
|
package/dist/esm/index.js
CHANGED
|
@@ -3,18 +3,34 @@
|
|
|
3
3
|
// src/GoogleMapsProvider.tsx
|
|
4
4
|
import * as React from "react";
|
|
5
5
|
import { useScript } from "@ttoss/react-hooks";
|
|
6
|
-
import { jsx } from "react/jsx-runtime";
|
|
6
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
7
7
|
var GoogleMapsContext = React.createContext({
|
|
8
8
|
status: "idle",
|
|
9
9
|
google: {
|
|
10
10
|
maps: null
|
|
11
11
|
}
|
|
12
12
|
});
|
|
13
|
+
var DefaultScript = ({
|
|
14
|
+
src,
|
|
15
|
+
onReady
|
|
16
|
+
}) => {
|
|
17
|
+
const {
|
|
18
|
+
status
|
|
19
|
+
} = useScript(src);
|
|
20
|
+
React.useEffect(() => {
|
|
21
|
+
if (status === "ready") {
|
|
22
|
+
onReady();
|
|
23
|
+
}
|
|
24
|
+
}, [status, onReady]);
|
|
25
|
+
return null;
|
|
26
|
+
};
|
|
13
27
|
var GoogleMapsProvider = ({
|
|
14
28
|
children,
|
|
15
29
|
apiKey,
|
|
16
30
|
libraries,
|
|
17
|
-
language
|
|
31
|
+
language,
|
|
32
|
+
Script = DefaultScript,
|
|
33
|
+
onError
|
|
18
34
|
}) => {
|
|
19
35
|
const src = (() => {
|
|
20
36
|
let srcTemp = `https://maps.googleapis.com/maps/api/js?key=${apiKey}`;
|
|
@@ -26,9 +42,7 @@ var GoogleMapsProvider = ({
|
|
|
26
42
|
}
|
|
27
43
|
return srcTemp;
|
|
28
44
|
})();
|
|
29
|
-
const
|
|
30
|
-
status
|
|
31
|
-
} = useScript(src);
|
|
45
|
+
const [status, setStatus] = React.useState("loading");
|
|
32
46
|
const google = React.useMemo(() => {
|
|
33
47
|
if (status === "ready" && window.google) {
|
|
34
48
|
return window.google;
|
|
@@ -51,9 +65,17 @@ var GoogleMapsProvider = ({
|
|
|
51
65
|
}
|
|
52
66
|
};
|
|
53
67
|
}, [google, status]);
|
|
54
|
-
return /* @__PURE__ */
|
|
55
|
-
|
|
56
|
-
|
|
68
|
+
return /* @__PURE__ */jsxs(Fragment, {
|
|
69
|
+
children: [/* @__PURE__ */jsx(Script, {
|
|
70
|
+
src,
|
|
71
|
+
onReady: () => {
|
|
72
|
+
return setStatus("ready");
|
|
73
|
+
},
|
|
74
|
+
onError
|
|
75
|
+
}), /* @__PURE__ */jsx(GoogleMapsContext.Provider, {
|
|
76
|
+
value,
|
|
77
|
+
children
|
|
78
|
+
})]
|
|
57
79
|
});
|
|
58
80
|
};
|
|
59
81
|
|
|
@@ -67,7 +89,9 @@ var useGoogleMaps = () => {
|
|
|
67
89
|
status,
|
|
68
90
|
google
|
|
69
91
|
} = React2.useContext(GoogleMapsContext);
|
|
92
|
+
const isReady = status === "ready";
|
|
70
93
|
return {
|
|
94
|
+
isReady,
|
|
71
95
|
status,
|
|
72
96
|
google,
|
|
73
97
|
/**
|
|
@@ -108,14 +132,25 @@ var useMap = (options = {}) => {
|
|
|
108
132
|
});
|
|
109
133
|
});
|
|
110
134
|
const {
|
|
111
|
-
google
|
|
135
|
+
google,
|
|
136
|
+
isReady
|
|
112
137
|
} = useGoogleMaps();
|
|
113
|
-
const map = React4.
|
|
114
|
-
|
|
115
|
-
|
|
138
|
+
const [map, setMap] = React4.useState(null);
|
|
139
|
+
React4.useEffect(() => {
|
|
140
|
+
if (map) {
|
|
141
|
+
return;
|
|
116
142
|
}
|
|
117
|
-
|
|
118
|
-
|
|
143
|
+
if (!ref.current) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
if (!isReady) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
if (!google.maps) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
setMap(new google.maps.Map(ref.current, options));
|
|
153
|
+
}, [map, isReady, ref, google.maps, options]);
|
|
119
154
|
const optionsStringify = JSON.stringify(options);
|
|
120
155
|
React4.useEffect(() => {
|
|
121
156
|
if (map) {
|
package/dist/index.d.ts
CHANGED
|
@@ -3,7 +3,12 @@ import * as React from 'react';
|
|
|
3
3
|
|
|
4
4
|
type GoogleMaps = typeof google.maps;
|
|
5
5
|
type Libraries = 'places' | 'visualization' | 'drawing' | 'geometry';
|
|
6
|
-
|
|
6
|
+
type ScriptProps = {
|
|
7
|
+
src: string;
|
|
8
|
+
onReady: () => void;
|
|
9
|
+
onError?: (e: Error) => void;
|
|
10
|
+
};
|
|
11
|
+
declare const GoogleMapsProvider: ({ children, apiKey, libraries, language, Script, onError, }: {
|
|
7
12
|
children: React.ReactNode;
|
|
8
13
|
apiKey: string;
|
|
9
14
|
libraries?: Libraries[];
|
|
@@ -11,6 +16,8 @@ declare const GoogleMapsProvider: ({ children, apiKey, libraries, language, }: {
|
|
|
11
16
|
* https://developers.google.com/maps/faq#languagesupport
|
|
12
17
|
*/
|
|
13
18
|
language?: string;
|
|
19
|
+
Script?: React.ComponentType<ScriptProps>;
|
|
20
|
+
onError?: (e: Error) => void;
|
|
14
21
|
}) => react_jsx_runtime.JSX.Element;
|
|
15
22
|
|
|
16
23
|
declare const useGeocoder: () => {
|
|
@@ -67,6 +74,7 @@ declare const usePlacesAutocomplete: ({ requestOptions, debounce, cache, cacheKe
|
|
|
67
74
|
* @returns An object containing the API status and the google object.
|
|
68
75
|
*/
|
|
69
76
|
declare const useGoogleMaps: () => {
|
|
77
|
+
isReady: boolean;
|
|
70
78
|
status: "idle" | "loading" | "ready" | "error";
|
|
71
79
|
google: {
|
|
72
80
|
maps: GoogleMaps;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ttoss/google-maps",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.7",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "ttoss",
|
|
6
6
|
"contributors": [
|
|
@@ -20,13 +20,12 @@
|
|
|
20
20
|
}
|
|
21
21
|
},
|
|
22
22
|
"files": [
|
|
23
|
-
"dist"
|
|
24
|
-
"src"
|
|
23
|
+
"dist"
|
|
25
24
|
],
|
|
26
25
|
"dependencies": {
|
|
27
26
|
"@types/google.maps": "^3.58.1",
|
|
28
27
|
"use-callback-ref": "^1.3.2",
|
|
29
|
-
"@ttoss/react-hooks": "^2.0.
|
|
28
|
+
"@ttoss/react-hooks": "^2.0.4"
|
|
30
29
|
},
|
|
31
30
|
"peerDependencies": {
|
|
32
31
|
"react": ">=16.8.0",
|
|
@@ -37,8 +36,8 @@
|
|
|
37
36
|
"jest": "^29.7.0",
|
|
38
37
|
"react": "^18.3.1",
|
|
39
38
|
"tsup": "^8.3.0",
|
|
40
|
-
"@ttoss/test-utils": "^2.1.
|
|
41
|
-
"@ttoss/config": "^1.34.
|
|
39
|
+
"@ttoss/test-utils": "^2.1.17",
|
|
40
|
+
"@ttoss/config": "^1.34.1"
|
|
42
41
|
},
|
|
43
42
|
"keywords": [
|
|
44
43
|
"Google",
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
import * as React from 'react';
|
|
2
|
-
import { ScriptStatus, useScript } from '@ttoss/react-hooks';
|
|
3
|
-
|
|
4
|
-
type Extends<T, U extends T> = U;
|
|
5
|
-
|
|
6
|
-
export type GoogleMaps = typeof google.maps;
|
|
7
|
-
|
|
8
|
-
type LoadedMapsStatus = Extends<ScriptStatus, 'ready'>;
|
|
9
|
-
|
|
10
|
-
type NotLoadedMapStatus = Extends<ScriptStatus, 'idle' | 'error' | 'loading'>;
|
|
11
|
-
|
|
12
|
-
export const GoogleMapsContext = React.createContext<
|
|
13
|
-
| {
|
|
14
|
-
status: LoadedMapsStatus;
|
|
15
|
-
google: {
|
|
16
|
-
maps: GoogleMaps;
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
| {
|
|
20
|
-
status: NotLoadedMapStatus;
|
|
21
|
-
google: {
|
|
22
|
-
maps: null;
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
>({
|
|
26
|
-
status: 'idle',
|
|
27
|
-
google: {
|
|
28
|
-
maps: null,
|
|
29
|
-
},
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
type Libraries = 'places' | 'visualization' | 'drawing' | 'geometry';
|
|
33
|
-
|
|
34
|
-
export const GoogleMapsProvider = ({
|
|
35
|
-
children,
|
|
36
|
-
apiKey,
|
|
37
|
-
libraries,
|
|
38
|
-
language,
|
|
39
|
-
}: {
|
|
40
|
-
children: React.ReactNode;
|
|
41
|
-
apiKey: string;
|
|
42
|
-
libraries?: Libraries[];
|
|
43
|
-
/**
|
|
44
|
-
* https://developers.google.com/maps/faq#languagesupport
|
|
45
|
-
*/
|
|
46
|
-
language?: string;
|
|
47
|
-
}) => {
|
|
48
|
-
const src = (() => {
|
|
49
|
-
let srcTemp = `https://maps.googleapis.com/maps/api/js?key=${apiKey}`;
|
|
50
|
-
|
|
51
|
-
if (libraries) {
|
|
52
|
-
srcTemp = srcTemp + `&libraries=${libraries.join(',')}`;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (language) {
|
|
56
|
-
srcTemp = srcTemp + `&language=${language}`;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return srcTemp;
|
|
60
|
-
})();
|
|
61
|
-
|
|
62
|
-
const { status } = useScript(src);
|
|
63
|
-
|
|
64
|
-
const google = React.useMemo(() => {
|
|
65
|
-
if (status === 'ready' && window.google) {
|
|
66
|
-
return window.google;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
return null;
|
|
70
|
-
}, [status]);
|
|
71
|
-
|
|
72
|
-
const value = React.useMemo(() => {
|
|
73
|
-
if (status === 'ready' && google?.maps) {
|
|
74
|
-
return {
|
|
75
|
-
status,
|
|
76
|
-
google: {
|
|
77
|
-
maps: google.maps,
|
|
78
|
-
},
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
return {
|
|
83
|
-
status: status as NotLoadedMapStatus,
|
|
84
|
-
google: {
|
|
85
|
-
maps: null,
|
|
86
|
-
},
|
|
87
|
-
};
|
|
88
|
-
}, [google, status]);
|
|
89
|
-
|
|
90
|
-
return (
|
|
91
|
-
<GoogleMapsContext.Provider value={value}>
|
|
92
|
-
{children}
|
|
93
|
-
</GoogleMapsContext.Provider>
|
|
94
|
-
);
|
|
95
|
-
};
|
package/src/index.ts
DELETED
package/src/typings.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
/// <reference types="google.maps" />
|
package/src/useGeocoder.ts
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import * as React from 'react';
|
|
2
|
-
import { useGoogleMaps } from './useGoogleMaps';
|
|
3
|
-
|
|
4
|
-
export const useGeocoder = () => {
|
|
5
|
-
const { google } = useGoogleMaps();
|
|
6
|
-
|
|
7
|
-
const [isGeocoderInitialized, setIsGeocoderInitialized] =
|
|
8
|
-
React.useState(false);
|
|
9
|
-
|
|
10
|
-
const geocoder = React.useMemo(() => {
|
|
11
|
-
if (google.maps) {
|
|
12
|
-
const googleMapsGeocoder = new google.maps.Geocoder();
|
|
13
|
-
setIsGeocoderInitialized(true);
|
|
14
|
-
return googleMapsGeocoder;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
return null;
|
|
18
|
-
}, [google.maps]);
|
|
19
|
-
|
|
20
|
-
return { geocoder, isGeocoderInitialized };
|
|
21
|
-
};
|
package/src/useGoogleMaps.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import * as React from 'react';
|
|
2
|
-
import { GoogleMapsContext } from './GoogleMapsProvider';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Returns the status of the Google Maps API and the google object which
|
|
6
|
-
* provides access to the [Google Maps API](https://developers.google.com/maps/documentation/javascript/overview).
|
|
7
|
-
*
|
|
8
|
-
* @returns An object containing the API status and the google object.
|
|
9
|
-
*/
|
|
10
|
-
export const useGoogleMaps = () => {
|
|
11
|
-
const { status, google } = React.useContext(GoogleMapsContext);
|
|
12
|
-
return {
|
|
13
|
-
status,
|
|
14
|
-
google,
|
|
15
|
-
/**
|
|
16
|
-
* @deprecated Use google.maps instead.
|
|
17
|
-
*/
|
|
18
|
-
googleMaps: google.maps,
|
|
19
|
-
};
|
|
20
|
-
};
|
package/src/useMap.ts
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import * as React from 'react';
|
|
2
|
-
import { useCallbackRef } from 'use-callback-ref';
|
|
3
|
-
import { useGoogleMaps } from './useGoogleMaps';
|
|
4
|
-
|
|
5
|
-
export const useMap = (options: google.maps.MapOptions = {}) => {
|
|
6
|
-
/**
|
|
7
|
-
* Read here for more details about the useCallbackRef hook:
|
|
8
|
-
* https://github.com/theKashey/use-callback-ref#usecallbackref---to-replace-reactuseref
|
|
9
|
-
*/
|
|
10
|
-
const [, forceUpdate] = React.useState(0);
|
|
11
|
-
|
|
12
|
-
const ref = useCallbackRef<HTMLDivElement>(null, () => {
|
|
13
|
-
return forceUpdate((n) => {
|
|
14
|
-
return n + 1;
|
|
15
|
-
});
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
const { google } = useGoogleMaps();
|
|
19
|
-
|
|
20
|
-
const map = React.useMemo(() => {
|
|
21
|
-
if (google?.maps && ref.current) {
|
|
22
|
-
return new google.maps.Map(ref.current, options);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
return null;
|
|
26
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
27
|
-
}, [google?.maps, ref.current]);
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* To avoid re-initializing the map because shallow object comparison.
|
|
31
|
-
* https://stackoverflow.com/a/62409962/8786986
|
|
32
|
-
*/
|
|
33
|
-
const optionsStringify = JSON.stringify(options);
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Update options but not reinitialize the map.
|
|
37
|
-
*/
|
|
38
|
-
React.useEffect(() => {
|
|
39
|
-
if (map) {
|
|
40
|
-
const parsedOptions = JSON.parse(optionsStringify);
|
|
41
|
-
map.setOptions(parsedOptions);
|
|
42
|
-
}
|
|
43
|
-
}, [optionsStringify, map]);
|
|
44
|
-
|
|
45
|
-
return {
|
|
46
|
-
/**
|
|
47
|
-
* Returns the map object which provides access to the [Google Maps API](https://developers.google.com/maps/documentation/javascript/overview).
|
|
48
|
-
*/
|
|
49
|
-
map,
|
|
50
|
-
/**
|
|
51
|
-
* Returns the ref object which provides access to the HTMLDivElement element
|
|
52
|
-
* that the map is rendered in.
|
|
53
|
-
*/
|
|
54
|
-
ref,
|
|
55
|
-
};
|
|
56
|
-
};
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
-
const debounce = <F extends (...args: any[]) => void>(
|
|
3
|
-
fn: F,
|
|
4
|
-
delay: number
|
|
5
|
-
): ((this: ThisParameterType<F>, ...args: Parameters<F>) => void) => {
|
|
6
|
-
let timer: ReturnType<typeof setTimeout> | null;
|
|
7
|
-
|
|
8
|
-
function debounceFn(this: ThisParameterType<F>, ...args: Parameters<F>) {
|
|
9
|
-
if (timer !== null) {
|
|
10
|
-
clearTimeout(timer);
|
|
11
|
-
timer = null;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
timer = setTimeout(() => fn.apply(this, args), delay);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
return debounceFn;
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
export default debounce;
|
|
@@ -1,208 +0,0 @@
|
|
|
1
|
-
/* eslint-disable react-hooks/exhaustive-deps */
|
|
2
|
-
import * as React from 'react';
|
|
3
|
-
import { useGoogleMaps } from './../useGoogleMaps';
|
|
4
|
-
import _debounce from './debounce';
|
|
5
|
-
import useLatest from './useLatest';
|
|
6
|
-
|
|
7
|
-
export interface HookArgs {
|
|
8
|
-
requestOptions?: Omit<google.maps.places.AutocompletionRequest, 'input'>;
|
|
9
|
-
debounce?: number;
|
|
10
|
-
cache?: number | false;
|
|
11
|
-
cacheKey?: string;
|
|
12
|
-
callbackName?: string;
|
|
13
|
-
defaultValue?: string;
|
|
14
|
-
initOnMount?: boolean;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
type Suggestion = google.maps.places.AutocompletePrediction;
|
|
18
|
-
|
|
19
|
-
type Status = `${google.maps.places.PlacesServiceStatus}` | '';
|
|
20
|
-
|
|
21
|
-
interface Suggestions {
|
|
22
|
-
readonly loading: boolean;
|
|
23
|
-
readonly status: Status;
|
|
24
|
-
data: Suggestion[];
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
interface SetValue {
|
|
28
|
-
(val: string, shouldFetchData?: boolean): void;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
interface HookReturn {
|
|
32
|
-
ready: boolean;
|
|
33
|
-
value: string;
|
|
34
|
-
suggestions: Suggestions;
|
|
35
|
-
setValue: SetValue;
|
|
36
|
-
clearSuggestions: () => void;
|
|
37
|
-
clearCache: () => void;
|
|
38
|
-
init: () => void;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export const loadApiErr = '💡 Google Maps Places API library must be loaded.';
|
|
42
|
-
|
|
43
|
-
export const usePlacesAutocomplete = ({
|
|
44
|
-
requestOptions,
|
|
45
|
-
debounce = 200,
|
|
46
|
-
cache = 24 * 60 * 60,
|
|
47
|
-
cacheKey,
|
|
48
|
-
callbackName,
|
|
49
|
-
defaultValue = '',
|
|
50
|
-
initOnMount = true,
|
|
51
|
-
}: HookArgs = {}): HookReturn => {
|
|
52
|
-
const [ready, setReady] = React.useState(false);
|
|
53
|
-
const [value, setVal] = React.useState(defaultValue);
|
|
54
|
-
const [suggestions, setSuggestions] = React.useState<Suggestions>({
|
|
55
|
-
loading: false,
|
|
56
|
-
status: '',
|
|
57
|
-
data: [],
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
61
|
-
const asRef = React.useRef<any>(null);
|
|
62
|
-
const requestOptionsRef = useLatest(requestOptions);
|
|
63
|
-
const { google } = useGoogleMaps();
|
|
64
|
-
|
|
65
|
-
const googleMapsRef = useLatest(google.maps);
|
|
66
|
-
|
|
67
|
-
const upaCacheKey = cacheKey ? `upa-${cacheKey}` : 'upa';
|
|
68
|
-
|
|
69
|
-
const init = React.useCallback(() => {
|
|
70
|
-
if (asRef.current) {
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
if (!google.maps) {
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const { current: gMaps } = googleMapsRef;
|
|
79
|
-
const placesLib = gMaps?.places || google.maps.places;
|
|
80
|
-
|
|
81
|
-
if (!placesLib) {
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
asRef.current = new placesLib.AutocompleteService();
|
|
86
|
-
setReady(true);
|
|
87
|
-
}, [google.maps]);
|
|
88
|
-
|
|
89
|
-
const clearSuggestions = React.useCallback(() => {
|
|
90
|
-
setSuggestions({ loading: false, status: '', data: [] });
|
|
91
|
-
}, []);
|
|
92
|
-
|
|
93
|
-
const clearCache = React.useCallback(() => {
|
|
94
|
-
try {
|
|
95
|
-
sessionStorage.removeItem(upaCacheKey);
|
|
96
|
-
} catch (error) {
|
|
97
|
-
// Skip exception
|
|
98
|
-
}
|
|
99
|
-
}, []);
|
|
100
|
-
|
|
101
|
-
const fetchPredictions = React.useCallback(
|
|
102
|
-
_debounce((val: string) => {
|
|
103
|
-
if (!val) {
|
|
104
|
-
clearSuggestions();
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
setSuggestions((prevState) => {
|
|
109
|
-
return { ...prevState, loading: true };
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
let cachedData: Record<string, { data: Suggestion[]; maxAge: number }> =
|
|
113
|
-
{};
|
|
114
|
-
|
|
115
|
-
try {
|
|
116
|
-
cachedData = JSON.parse(sessionStorage.getItem(upaCacheKey) || '{}');
|
|
117
|
-
} catch (error) {
|
|
118
|
-
// Skip exception
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
if (cache) {
|
|
122
|
-
cachedData = Object.keys(cachedData).reduce(
|
|
123
|
-
(acc: typeof cachedData, key) => {
|
|
124
|
-
if (cachedData[key].maxAge - Date.now() >= 0) {
|
|
125
|
-
acc[key] = cachedData[key];
|
|
126
|
-
}
|
|
127
|
-
return acc;
|
|
128
|
-
},
|
|
129
|
-
{}
|
|
130
|
-
);
|
|
131
|
-
|
|
132
|
-
if (cachedData[val]) {
|
|
133
|
-
setSuggestions({
|
|
134
|
-
loading: false,
|
|
135
|
-
status: 'OK',
|
|
136
|
-
data: cachedData[val].data,
|
|
137
|
-
});
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
asRef?.current?.getPlacePredictions(
|
|
143
|
-
{ ...requestOptionsRef.current, input: val },
|
|
144
|
-
(data: Suggestion[] | null, status: Status) => {
|
|
145
|
-
setSuggestions({ loading: false, status, data: data || [] });
|
|
146
|
-
|
|
147
|
-
if (cache && status === 'OK') {
|
|
148
|
-
cachedData[val] = {
|
|
149
|
-
data: data as Suggestion[],
|
|
150
|
-
maxAge: Date.now() + cache * 1000,
|
|
151
|
-
};
|
|
152
|
-
|
|
153
|
-
try {
|
|
154
|
-
sessionStorage.setItem(upaCacheKey, JSON.stringify(cachedData));
|
|
155
|
-
} catch (error) {
|
|
156
|
-
// Skip exception
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
);
|
|
161
|
-
}, debounce),
|
|
162
|
-
[debounce, clearSuggestions]
|
|
163
|
-
);
|
|
164
|
-
|
|
165
|
-
const setValue: SetValue = React.useCallback(
|
|
166
|
-
(val, shouldFetchData = true) => {
|
|
167
|
-
setVal(val);
|
|
168
|
-
if (asRef.current && shouldFetchData) {
|
|
169
|
-
fetchPredictions(val);
|
|
170
|
-
}
|
|
171
|
-
},
|
|
172
|
-
[fetchPredictions]
|
|
173
|
-
);
|
|
174
|
-
|
|
175
|
-
React.useEffect(() => {
|
|
176
|
-
if (!initOnMount) {
|
|
177
|
-
// eslint-disable-next-line react/display-name
|
|
178
|
-
return () => {
|
|
179
|
-
return null;
|
|
180
|
-
};
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
if (!googleMapsRef.current && !google.maps && callbackName) {
|
|
184
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
185
|
-
(window as any)[callbackName] = init;
|
|
186
|
-
} else {
|
|
187
|
-
init();
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
return () => {
|
|
191
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
192
|
-
if ((window as any)[callbackName as string]) {
|
|
193
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
194
|
-
delete (window as any)[callbackName as string];
|
|
195
|
-
}
|
|
196
|
-
};
|
|
197
|
-
}, [callbackName, init]);
|
|
198
|
-
|
|
199
|
-
return {
|
|
200
|
-
ready,
|
|
201
|
-
value,
|
|
202
|
-
suggestions,
|
|
203
|
-
setValue,
|
|
204
|
-
clearSuggestions,
|
|
205
|
-
clearCache,
|
|
206
|
-
init,
|
|
207
|
-
};
|
|
208
|
-
};
|