Really, really odd behaviour when storing google maps API in atom
See original GitHub issueI’m not 100% sure this is a Recoil issue, but I have an app that users may or may not click on a google maps route. In that case, I dynamically load the API. However, once the API is loaded, I see no reason to load it again if users switches between the pages, hence I stored it in an Recoil atom. However, when doing so, street view won’t work (there’s an exception ,more about that later).
So firstly, the code of the atom (pretty simple):
import { atom } from 'recoil';
export const googleApiState = atom({
key: 'googleApiState',
default: null,
});
Afterwards, here’s my custom hook:
import { useState, useEffect } from 'react';
import { apiScriptLoader } from '../../utils';
import { useRecoilState } from 'recoil';
import { googleApiState } from '../../state';
export function useGoogleMapsApi(mapsCfg) {
const [google, setGoogle] = useRecoilState(googleApiState);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const mounted = async () => {
if (!google) {
try {
const response = await apiScriptLoader(mapsCfg);
setGoogle(response);
setLoading(false);
} catch (error) {
setError(error.message);
setLoading(false);
}
} else {
setLoading(false);
}
};
mounted();
}, []);
return {
google,
error,
loading,
};
}
Here’s the code for the API script loader:
let api = {};
function loadScript(cfg) {
const script = document.createElement('script');
script.type = 'text/javascript';
script.async = true;
script.defer = true;
const scriptHref = new URL(cfg.baseUrl);
for (const key in cfg.params) {
scriptHref.searchParams.append(key, cfg.params[key]);
}
script.src = scriptHref.href;
document.head.appendChild(script);
}
export function apiScriptLoader(cfg) {
const type = cfg.type;
return new Promise((resolve, reject) => {
if (api[type]) return resolve(api[type]);
try {
loadScript(cfg);
window[cfg.callback] = () => {
api[type] = window[type];
resolve(api[type]);
};
setTimeout(() => {
if (!window[type]) {
reject('Loading took too long');
}
}, 5000);
} catch (error) {
reject(error);
}
});
}
Now when I try to render google map, it WORKS as expected. I can use Markers, whatever. Each time I want to access the API, I can use this custom hook and the script is not loaded again.
However, when I try to drag and drop that marker for the street view, I get this error: streetview.js:38 Uncaught (in promise) TypeError: h.i is not a function at streetview.js:38
Now this is really, really weird, since the map works fine, markers work, drag and drop events work, just not the street view.
However, when I change this part: const [google, setGoogle] = useRecoilState(googleApiState);
with this: const [google, setGoogle] = useState();
everything works as expected.
So I thought that the problem is in the google maps API not being injected in the HTML, but that’s not the case. I even changed mounted code to this, so that the script loader is always called:
const mounted = async () => {
try {
const response = await apiScriptLoader(mapsCfg);
setGoogle(response);
setLoading(false);
} catch (error) {
console.log(error);
setError(error.message);
setLoading(false);
}
};
Still no luck.
I’m not exactly sure where the problem is (it seems a bit odd that only street view doesn’t work, maps do!), but currently I solved this by not using useRecoilState… Any ideas?
Issue Analytics
- State:
- Created 3 years ago
- Comments:7 (1 by maintainers)
Top GitHub Comments
Yes! This did solve the issue, thanks @wuzzeb @drarmstr so I guess that freezing the object does temper with google maps api, especially street view. Nice to know!
@javascrewpt - Did using
dangerouslyAllowMutability
resolve your issue?