@ttoss/google-maps 1.16.1 → 1.20.1
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/dist/esm/index.js +145 -1
- package/dist/index.d.ts +31 -1
- package/dist/index.js +146 -2
- package/package.json +8 -4
- package/src/index.spec.ts +1 -0
- package/src/index.ts +1 -0
- package/src/usePlacesAutocomplete/debounce.ts +20 -0
- package/src/usePlacesAutocomplete/index.ts +193 -0
- package/src/usePlacesAutocomplete/useLatest.ts +7 -0
package/dist/esm/index.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
1
3
|
// tsup.inject.js
|
|
2
4
|
import * as React from "react";
|
|
3
5
|
|
|
@@ -90,9 +92,151 @@ var useMap = (options = {}) => {
|
|
|
90
92
|
ref
|
|
91
93
|
};
|
|
92
94
|
};
|
|
95
|
+
|
|
96
|
+
// src/usePlacesAutocomplete/index.ts
|
|
97
|
+
import { useCallback, useEffect as useEffect2, useRef as useRef2, useState as useState3 } from "react";
|
|
98
|
+
|
|
99
|
+
// src/usePlacesAutocomplete/debounce.ts
|
|
100
|
+
var debounce = (fn, delay) => {
|
|
101
|
+
let timer;
|
|
102
|
+
function debounceFn(...args) {
|
|
103
|
+
if (timer !== null) {
|
|
104
|
+
clearTimeout(timer);
|
|
105
|
+
timer = null;
|
|
106
|
+
}
|
|
107
|
+
timer = setTimeout(() => fn.apply(this, args), delay);
|
|
108
|
+
}
|
|
109
|
+
return debounceFn;
|
|
110
|
+
};
|
|
111
|
+
var debounce_default = debounce;
|
|
112
|
+
|
|
113
|
+
// src/usePlacesAutocomplete/useLatest.ts
|
|
114
|
+
import { useRef } from "react";
|
|
115
|
+
var useLatest_default = (val) => {
|
|
116
|
+
const ref = useRef(val);
|
|
117
|
+
ref.current = val;
|
|
118
|
+
return ref;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
// src/usePlacesAutocomplete/index.ts
|
|
122
|
+
var usePlacesAutocomplete = ({
|
|
123
|
+
requestOptions,
|
|
124
|
+
debounce: debounce2 = 200,
|
|
125
|
+
cache = 24 * 60 * 60,
|
|
126
|
+
cacheKey,
|
|
127
|
+
callbackName,
|
|
128
|
+
defaultValue = "",
|
|
129
|
+
initOnMount = true
|
|
130
|
+
} = {}) => {
|
|
131
|
+
const [ready, setReady] = useState3(false);
|
|
132
|
+
const [value, setVal] = useState3(defaultValue);
|
|
133
|
+
const [suggestions, setSuggestions] = useState3({
|
|
134
|
+
loading: false,
|
|
135
|
+
status: "",
|
|
136
|
+
data: []
|
|
137
|
+
});
|
|
138
|
+
const asRef = useRef2(null);
|
|
139
|
+
const requestOptionsRef = useLatest_default(requestOptions);
|
|
140
|
+
const { googleMaps } = useGoogleMaps();
|
|
141
|
+
const googleMapsRef = useLatest_default(googleMaps);
|
|
142
|
+
const upaCacheKey = cacheKey ? `upa-${cacheKey}` : "upa";
|
|
143
|
+
const init = useCallback(() => {
|
|
144
|
+
if (asRef.current)
|
|
145
|
+
return;
|
|
146
|
+
if (!googleMaps) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
const { current: gMaps } = googleMapsRef;
|
|
150
|
+
const placesLib = (gMaps == null ? void 0 : gMaps.places) || googleMaps.places;
|
|
151
|
+
if (!placesLib) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
asRef.current = new placesLib.AutocompleteService();
|
|
155
|
+
setReady(true);
|
|
156
|
+
}, [googleMaps]);
|
|
157
|
+
const clearSuggestions = useCallback(() => {
|
|
158
|
+
setSuggestions({ loading: false, status: "", data: [] });
|
|
159
|
+
}, []);
|
|
160
|
+
const clearCache = useCallback(() => {
|
|
161
|
+
try {
|
|
162
|
+
sessionStorage.removeItem(upaCacheKey);
|
|
163
|
+
} catch (error) {
|
|
164
|
+
}
|
|
165
|
+
}, []);
|
|
166
|
+
const fetchPredictions = useCallback(debounce_default((val) => {
|
|
167
|
+
var _a;
|
|
168
|
+
if (!val) {
|
|
169
|
+
clearSuggestions();
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
setSuggestions((prevState) => ({ ...prevState, loading: true }));
|
|
173
|
+
let cachedData = {};
|
|
174
|
+
try {
|
|
175
|
+
cachedData = JSON.parse(sessionStorage.getItem(upaCacheKey) || "{}");
|
|
176
|
+
} catch (error) {
|
|
177
|
+
}
|
|
178
|
+
if (cache) {
|
|
179
|
+
cachedData = Object.keys(cachedData).reduce((acc, key) => {
|
|
180
|
+
if (cachedData[key].maxAge - Date.now() >= 0)
|
|
181
|
+
acc[key] = cachedData[key];
|
|
182
|
+
return acc;
|
|
183
|
+
}, {});
|
|
184
|
+
if (cachedData[val]) {
|
|
185
|
+
setSuggestions({
|
|
186
|
+
loading: false,
|
|
187
|
+
status: "OK",
|
|
188
|
+
data: cachedData[val].data
|
|
189
|
+
});
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
(_a = asRef == null ? void 0 : asRef.current) == null ? void 0 : _a.getPlacePredictions({ ...requestOptionsRef.current, input: val }, (data, status) => {
|
|
194
|
+
setSuggestions({ loading: false, status, data: data || [] });
|
|
195
|
+
if (cache && status === "OK") {
|
|
196
|
+
cachedData[val] = {
|
|
197
|
+
data,
|
|
198
|
+
maxAge: Date.now() + cache * 1e3
|
|
199
|
+
};
|
|
200
|
+
try {
|
|
201
|
+
sessionStorage.setItem(upaCacheKey, JSON.stringify(cachedData));
|
|
202
|
+
} catch (error) {
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
}, debounce2), [debounce2, clearSuggestions]);
|
|
207
|
+
const setValue = useCallback((val, shouldFetchData = true) => {
|
|
208
|
+
setVal(val);
|
|
209
|
+
if (asRef.current && shouldFetchData)
|
|
210
|
+
fetchPredictions(val);
|
|
211
|
+
}, [fetchPredictions]);
|
|
212
|
+
useEffect2(() => {
|
|
213
|
+
if (!initOnMount) {
|
|
214
|
+
return () => null;
|
|
215
|
+
}
|
|
216
|
+
if (!googleMapsRef.current && !googleMaps && callbackName) {
|
|
217
|
+
window[callbackName] = init;
|
|
218
|
+
} else {
|
|
219
|
+
init();
|
|
220
|
+
}
|
|
221
|
+
return () => {
|
|
222
|
+
if (window[callbackName])
|
|
223
|
+
delete window[callbackName];
|
|
224
|
+
};
|
|
225
|
+
}, [callbackName, init]);
|
|
226
|
+
return {
|
|
227
|
+
ready,
|
|
228
|
+
value,
|
|
229
|
+
suggestions,
|
|
230
|
+
setValue,
|
|
231
|
+
clearSuggestions,
|
|
232
|
+
clearCache,
|
|
233
|
+
init
|
|
234
|
+
};
|
|
235
|
+
};
|
|
93
236
|
export {
|
|
94
237
|
GoogleMapsProvider,
|
|
95
238
|
useGeocoder,
|
|
96
239
|
useGoogleMaps,
|
|
97
|
-
useMap
|
|
240
|
+
useMap,
|
|
241
|
+
usePlacesAutocomplete
|
|
98
242
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -44,4 +44,34 @@ declare const useMap: (options?: google.maps.MapOptions) => {
|
|
|
44
44
|
ref: React.MutableRefObject<HTMLDivElement | null>;
|
|
45
45
|
};
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
interface HookArgs {
|
|
48
|
+
requestOptions?: Omit<google.maps.places.AutocompletionRequest, 'input'>;
|
|
49
|
+
debounce?: number;
|
|
50
|
+
cache?: number | false;
|
|
51
|
+
cacheKey?: string;
|
|
52
|
+
callbackName?: string;
|
|
53
|
+
defaultValue?: string;
|
|
54
|
+
initOnMount?: boolean;
|
|
55
|
+
}
|
|
56
|
+
declare type Suggestion = google.maps.places.AutocompletePrediction;
|
|
57
|
+
declare type Status = `${google.maps.places.PlacesServiceStatus}` | '';
|
|
58
|
+
interface Suggestions {
|
|
59
|
+
readonly loading: boolean;
|
|
60
|
+
readonly status: Status;
|
|
61
|
+
data: Suggestion[];
|
|
62
|
+
}
|
|
63
|
+
interface SetValue {
|
|
64
|
+
(val: string, shouldFetchData?: boolean): void;
|
|
65
|
+
}
|
|
66
|
+
interface HookReturn {
|
|
67
|
+
ready: boolean;
|
|
68
|
+
value: string;
|
|
69
|
+
suggestions: Suggestions;
|
|
70
|
+
setValue: SetValue;
|
|
71
|
+
clearSuggestions: () => void;
|
|
72
|
+
clearCache: () => void;
|
|
73
|
+
init: () => void;
|
|
74
|
+
}
|
|
75
|
+
declare const usePlacesAutocomplete: ({ requestOptions, debounce, cache, cacheKey, callbackName, defaultValue, initOnMount, }?: HookArgs) => HookReturn;
|
|
76
|
+
|
|
77
|
+
export { GoogleMapsProvider, useGeocoder, useGoogleMaps, useMap, usePlacesAutocomplete };
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
"use strict";
|
|
1
2
|
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
@@ -25,7 +26,8 @@ __export(src_exports, {
|
|
|
25
26
|
GoogleMapsProvider: () => GoogleMapsProvider,
|
|
26
27
|
useGeocoder: () => useGeocoder,
|
|
27
28
|
useGoogleMaps: () => useGoogleMaps,
|
|
28
|
-
useMap: () => useMap
|
|
29
|
+
useMap: () => useMap,
|
|
30
|
+
usePlacesAutocomplete: () => usePlacesAutocomplete
|
|
29
31
|
});
|
|
30
32
|
module.exports = __toCommonJS(src_exports);
|
|
31
33
|
|
|
@@ -121,10 +123,152 @@ var useMap = (options = {}) => {
|
|
|
121
123
|
ref
|
|
122
124
|
};
|
|
123
125
|
};
|
|
126
|
+
|
|
127
|
+
// src/usePlacesAutocomplete/index.ts
|
|
128
|
+
var import_react2 = require("react");
|
|
129
|
+
|
|
130
|
+
// src/usePlacesAutocomplete/debounce.ts
|
|
131
|
+
var debounce = (fn, delay) => {
|
|
132
|
+
let timer;
|
|
133
|
+
function debounceFn(...args) {
|
|
134
|
+
if (timer !== null) {
|
|
135
|
+
clearTimeout(timer);
|
|
136
|
+
timer = null;
|
|
137
|
+
}
|
|
138
|
+
timer = setTimeout(() => fn.apply(this, args), delay);
|
|
139
|
+
}
|
|
140
|
+
return debounceFn;
|
|
141
|
+
};
|
|
142
|
+
var debounce_default = debounce;
|
|
143
|
+
|
|
144
|
+
// src/usePlacesAutocomplete/useLatest.ts
|
|
145
|
+
var import_react = require("react");
|
|
146
|
+
var useLatest_default = (val) => {
|
|
147
|
+
const ref = (0, import_react.useRef)(val);
|
|
148
|
+
ref.current = val;
|
|
149
|
+
return ref;
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
// src/usePlacesAutocomplete/index.ts
|
|
153
|
+
var usePlacesAutocomplete = ({
|
|
154
|
+
requestOptions,
|
|
155
|
+
debounce: debounce2 = 200,
|
|
156
|
+
cache = 24 * 60 * 60,
|
|
157
|
+
cacheKey,
|
|
158
|
+
callbackName,
|
|
159
|
+
defaultValue = "",
|
|
160
|
+
initOnMount = true
|
|
161
|
+
} = {}) => {
|
|
162
|
+
const [ready, setReady] = (0, import_react2.useState)(false);
|
|
163
|
+
const [value, setVal] = (0, import_react2.useState)(defaultValue);
|
|
164
|
+
const [suggestions, setSuggestions] = (0, import_react2.useState)({
|
|
165
|
+
loading: false,
|
|
166
|
+
status: "",
|
|
167
|
+
data: []
|
|
168
|
+
});
|
|
169
|
+
const asRef = (0, import_react2.useRef)(null);
|
|
170
|
+
const requestOptionsRef = useLatest_default(requestOptions);
|
|
171
|
+
const { googleMaps } = useGoogleMaps();
|
|
172
|
+
const googleMapsRef = useLatest_default(googleMaps);
|
|
173
|
+
const upaCacheKey = cacheKey ? `upa-${cacheKey}` : "upa";
|
|
174
|
+
const init = (0, import_react2.useCallback)(() => {
|
|
175
|
+
if (asRef.current)
|
|
176
|
+
return;
|
|
177
|
+
if (!googleMaps) {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
const { current: gMaps } = googleMapsRef;
|
|
181
|
+
const placesLib = (gMaps == null ? void 0 : gMaps.places) || googleMaps.places;
|
|
182
|
+
if (!placesLib) {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
asRef.current = new placesLib.AutocompleteService();
|
|
186
|
+
setReady(true);
|
|
187
|
+
}, [googleMaps]);
|
|
188
|
+
const clearSuggestions = (0, import_react2.useCallback)(() => {
|
|
189
|
+
setSuggestions({ loading: false, status: "", data: [] });
|
|
190
|
+
}, []);
|
|
191
|
+
const clearCache = (0, import_react2.useCallback)(() => {
|
|
192
|
+
try {
|
|
193
|
+
sessionStorage.removeItem(upaCacheKey);
|
|
194
|
+
} catch (error) {
|
|
195
|
+
}
|
|
196
|
+
}, []);
|
|
197
|
+
const fetchPredictions = (0, import_react2.useCallback)(debounce_default((val) => {
|
|
198
|
+
var _a;
|
|
199
|
+
if (!val) {
|
|
200
|
+
clearSuggestions();
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
setSuggestions((prevState) => ({ ...prevState, loading: true }));
|
|
204
|
+
let cachedData = {};
|
|
205
|
+
try {
|
|
206
|
+
cachedData = JSON.parse(sessionStorage.getItem(upaCacheKey) || "{}");
|
|
207
|
+
} catch (error) {
|
|
208
|
+
}
|
|
209
|
+
if (cache) {
|
|
210
|
+
cachedData = Object.keys(cachedData).reduce((acc, key) => {
|
|
211
|
+
if (cachedData[key].maxAge - Date.now() >= 0)
|
|
212
|
+
acc[key] = cachedData[key];
|
|
213
|
+
return acc;
|
|
214
|
+
}, {});
|
|
215
|
+
if (cachedData[val]) {
|
|
216
|
+
setSuggestions({
|
|
217
|
+
loading: false,
|
|
218
|
+
status: "OK",
|
|
219
|
+
data: cachedData[val].data
|
|
220
|
+
});
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
(_a = asRef == null ? void 0 : asRef.current) == null ? void 0 : _a.getPlacePredictions({ ...requestOptionsRef.current, input: val }, (data, status) => {
|
|
225
|
+
setSuggestions({ loading: false, status, data: data || [] });
|
|
226
|
+
if (cache && status === "OK") {
|
|
227
|
+
cachedData[val] = {
|
|
228
|
+
data,
|
|
229
|
+
maxAge: Date.now() + cache * 1e3
|
|
230
|
+
};
|
|
231
|
+
try {
|
|
232
|
+
sessionStorage.setItem(upaCacheKey, JSON.stringify(cachedData));
|
|
233
|
+
} catch (error) {
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
}, debounce2), [debounce2, clearSuggestions]);
|
|
238
|
+
const setValue = (0, import_react2.useCallback)((val, shouldFetchData = true) => {
|
|
239
|
+
setVal(val);
|
|
240
|
+
if (asRef.current && shouldFetchData)
|
|
241
|
+
fetchPredictions(val);
|
|
242
|
+
}, [fetchPredictions]);
|
|
243
|
+
(0, import_react2.useEffect)(() => {
|
|
244
|
+
if (!initOnMount) {
|
|
245
|
+
return () => null;
|
|
246
|
+
}
|
|
247
|
+
if (!googleMapsRef.current && !googleMaps && callbackName) {
|
|
248
|
+
window[callbackName] = init;
|
|
249
|
+
} else {
|
|
250
|
+
init();
|
|
251
|
+
}
|
|
252
|
+
return () => {
|
|
253
|
+
if (window[callbackName])
|
|
254
|
+
delete window[callbackName];
|
|
255
|
+
};
|
|
256
|
+
}, [callbackName, init]);
|
|
257
|
+
return {
|
|
258
|
+
ready,
|
|
259
|
+
value,
|
|
260
|
+
suggestions,
|
|
261
|
+
setValue,
|
|
262
|
+
clearSuggestions,
|
|
263
|
+
clearCache,
|
|
264
|
+
init
|
|
265
|
+
};
|
|
266
|
+
};
|
|
124
267
|
// Annotate the CommonJS export names for ESM import in node:
|
|
125
268
|
0 && (module.exports = {
|
|
126
269
|
GoogleMapsProvider,
|
|
127
270
|
useGeocoder,
|
|
128
271
|
useGoogleMaps,
|
|
129
|
-
useMap
|
|
272
|
+
useMap,
|
|
273
|
+
usePlacesAutocomplete
|
|
130
274
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ttoss/google-maps",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.20.1",
|
|
4
4
|
"author": "ttoss",
|
|
5
5
|
"contributors": [
|
|
6
6
|
{
|
|
@@ -31,13 +31,17 @@
|
|
|
31
31
|
"build": "tsup"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@ttoss/hooks": "^1.
|
|
35
|
-
"@types/google.maps": "^3.
|
|
34
|
+
"@ttoss/hooks": "^1.20.0",
|
|
35
|
+
"@types/google.maps": "^3.49.2",
|
|
36
36
|
"use-callback-ref": "^1.3.0"
|
|
37
37
|
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@ttoss/config": "^1.17.0",
|
|
40
|
+
"@ttoss/test-utils": "^1.16.6"
|
|
41
|
+
},
|
|
38
42
|
"peerDependencies": {
|
|
39
43
|
"react": ">=16.8.0",
|
|
40
44
|
"react-dom": ">=16.8.0"
|
|
41
45
|
},
|
|
42
|
-
"gitHead": "
|
|
46
|
+
"gitHead": "587a0fa017ce16add11728db7365a41e9650685d"
|
|
43
47
|
}
|
package/src/index.spec.ts
CHANGED
package/src/index.ts
CHANGED
|
@@ -0,0 +1,20 @@
|
|
|
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;
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/* eslint-disable react-hooks/exhaustive-deps */
|
|
2
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
3
|
+
import { useGoogleMaps } from './../GoogleMapsProvider';
|
|
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] = useState(false);
|
|
53
|
+
const [value, setVal] = useState(defaultValue);
|
|
54
|
+
const [suggestions, setSuggestions] = useState<Suggestions>({
|
|
55
|
+
loading: false,
|
|
56
|
+
status: '',
|
|
57
|
+
data: [],
|
|
58
|
+
});
|
|
59
|
+
const asRef = useRef<any>(null);
|
|
60
|
+
const requestOptionsRef = useLatest(requestOptions);
|
|
61
|
+
const { googleMaps } = useGoogleMaps();
|
|
62
|
+
|
|
63
|
+
const googleMapsRef = useLatest(googleMaps);
|
|
64
|
+
|
|
65
|
+
const upaCacheKey = cacheKey ? `upa-${cacheKey}` : 'upa';
|
|
66
|
+
|
|
67
|
+
const init = useCallback(() => {
|
|
68
|
+
if (asRef.current) return;
|
|
69
|
+
|
|
70
|
+
if (!googleMaps) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const { current: gMaps } = googleMapsRef;
|
|
75
|
+
const placesLib = gMaps?.places || googleMaps.places;
|
|
76
|
+
|
|
77
|
+
if (!placesLib) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
asRef.current = new placesLib.AutocompleteService();
|
|
82
|
+
setReady(true);
|
|
83
|
+
}, [googleMaps]);
|
|
84
|
+
|
|
85
|
+
const clearSuggestions = useCallback(() => {
|
|
86
|
+
setSuggestions({ loading: false, status: '', data: [] });
|
|
87
|
+
}, []);
|
|
88
|
+
|
|
89
|
+
const clearCache = useCallback(() => {
|
|
90
|
+
try {
|
|
91
|
+
sessionStorage.removeItem(upaCacheKey);
|
|
92
|
+
} catch (error) {
|
|
93
|
+
// Skip exception
|
|
94
|
+
}
|
|
95
|
+
}, []);
|
|
96
|
+
|
|
97
|
+
const fetchPredictions = useCallback(
|
|
98
|
+
_debounce((val: string) => {
|
|
99
|
+
if (!val) {
|
|
100
|
+
clearSuggestions();
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
setSuggestions((prevState) => ({ ...prevState, loading: true }));
|
|
105
|
+
|
|
106
|
+
let cachedData: Record<string, { data: Suggestion[]; maxAge: number }> =
|
|
107
|
+
{};
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
cachedData = JSON.parse(sessionStorage.getItem(upaCacheKey) || '{}');
|
|
111
|
+
} catch (error) {
|
|
112
|
+
// Skip exception
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (cache) {
|
|
116
|
+
cachedData = Object.keys(cachedData).reduce(
|
|
117
|
+
(acc: typeof cachedData, key) => {
|
|
118
|
+
if (cachedData[key].maxAge - Date.now() >= 0)
|
|
119
|
+
acc[key] = cachedData[key];
|
|
120
|
+
return acc;
|
|
121
|
+
},
|
|
122
|
+
{}
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
if (cachedData[val]) {
|
|
126
|
+
setSuggestions({
|
|
127
|
+
loading: false,
|
|
128
|
+
status: 'OK',
|
|
129
|
+
data: cachedData[val].data,
|
|
130
|
+
});
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
asRef?.current?.getPlacePredictions(
|
|
136
|
+
{ ...requestOptionsRef.current, input: val },
|
|
137
|
+
(data: Suggestion[] | null, status: Status) => {
|
|
138
|
+
setSuggestions({ loading: false, status, data: data || [] });
|
|
139
|
+
|
|
140
|
+
if (cache && status === 'OK') {
|
|
141
|
+
cachedData[val] = {
|
|
142
|
+
data: data as Suggestion[],
|
|
143
|
+
maxAge: Date.now() + cache * 1000,
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
sessionStorage.setItem(upaCacheKey, JSON.stringify(cachedData));
|
|
148
|
+
} catch (error) {
|
|
149
|
+
// Skip exception
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
);
|
|
154
|
+
}, debounce),
|
|
155
|
+
[debounce, clearSuggestions]
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
const setValue: SetValue = useCallback(
|
|
159
|
+
(val, shouldFetchData = true) => {
|
|
160
|
+
setVal(val);
|
|
161
|
+
if (asRef.current && shouldFetchData) fetchPredictions(val);
|
|
162
|
+
},
|
|
163
|
+
[fetchPredictions]
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
useEffect(() => {
|
|
167
|
+
if (!initOnMount) {
|
|
168
|
+
// eslint-disable-next-line react/display-name
|
|
169
|
+
return () => null;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (!googleMapsRef.current && !googleMaps && callbackName) {
|
|
173
|
+
(window as any)[callbackName] = init;
|
|
174
|
+
} else {
|
|
175
|
+
init();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return () => {
|
|
179
|
+
if ((window as any)[callbackName as string])
|
|
180
|
+
delete (window as any)[callbackName as string];
|
|
181
|
+
};
|
|
182
|
+
}, [callbackName, init]);
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
ready,
|
|
186
|
+
value,
|
|
187
|
+
suggestions,
|
|
188
|
+
setValue,
|
|
189
|
+
clearSuggestions,
|
|
190
|
+
clearCache,
|
|
191
|
+
init,
|
|
192
|
+
};
|
|
193
|
+
};
|