I need to know whether the user clicked on "Allow" or "Deny" while accessing a webpage built using React+Typescript but so far I have been unable to get it working properly. See the following piece of code:
import { useEffect, useState } from "react";
import useGeolocation from "./useGeolocation";
export default function App() {
const [locationAccess, setLocationAccess] = useState<boolean>(false);
useEffect(() => {
navigator.permissions
.query({ name: "geolocation" })
.then((permissionStatus) => {
setLocationAccess(permissionStatus.state === "granted");
});
}, [locationAccess]);
const geoLocation = useGeolocation({ locationAccess });
console.log("geoLocation", geoLocation);
return <></>;
}
When I access the page for the first time and you are requested to give permission to access your geolocation I am able to see the following object being return:
{
"loaded": false,
"coordinates": {
"lat": "",
"lng": ""
},
"locale": "",
"countryCode": "",
"error": {}
}
which is fine because useGeolocation
does return such an object if no access has been allowed| or denied. Now if I click on "Allow" I would expect right away to see the following object being returned:
{
"loaded": true,
"coordinates": {
"lat": 28.504016,
"lng": -82.5510363
},
"locale": "en-us",
"countryCode": "US",
"error": {}
}
but instead, I have to reload the page to get the values back. Is it possible to achieve this once the user clicks the "Allow" button?
I have set up a CodeSanbox if you need access to the code and to play with it here
When the PermissionStatus
's state is "prompt"
, you're supposed to wait for its onchange
event to actually know if it has been "granted"
or "denied"
.
So you need to modify your code to look like
useEffect(() => {
navigator.permissions
.query({ name: "geolocation" })
.then((permissionStatus) => {
if (permissionStatus.state === "prompt") {
permissionStatus.onchange = (evt) => {
setLocationAccess(permissionStatus.state === "granted");
};
}
else { // User has already saved a setting
setLocationAccess(permissionStatus.state === "granted");
}
});
}, [locationAccess]);
PS: It seems that contrarily to what MDN's browser-compat data states, a few browsers do not support the change
event here. One workaround for the geolocation one can be found in this answer by user Kuday, which does ab-use the fact that a call to getCurrentPosition()
will wait fort the user's response before either resolving or erroring.
We can rewrite their solution in a more practical way which will return a Promise resolving with a Boolean stating whether the permission has been granted or denied:
async function requestLocationPermission() {
const queryResult = await navigator.permissions.query({
name: "geolocation"
});
if (queryResult.state === "denied") {
return false;
}
if (queryResult.state === "granted") {
return true;
}
// Whatever comes first between the actual event (Chrome)
// or the request to getCurrentPosition() (others).
return Promise.race([
new Promise((resolve) => {
navigator.geolocation
.getCurrentPosition(() => resolve(true), (err) => resolve(false));
}),
new Promise((resolve) => {
queryResult.onchange = () => resolve(queryResult.state === "granted");
}),
]);
}
// Usage:
const hasAccess = await requestLocationPermission();
if (hasAccess) {
// do your stuff
}