Home > Mobile >  I've created a hook that will read my browser's device permissions, but it re-renders cons
I've created a hook that will read my browser's device permissions, but it re-renders cons

Time:02-03

So I've created a hook that will read my browser's devices and their permissions:

/**
 * @param {PermissionName} name The permission name will be checked. Will be one of
 * "geolocation" | "notifications" | "persistent-storage" | "push" | "screen-wake-lock" | "xr-spatial-tracking";
 * @returns {PermissionStatus} permission
 */
export const useCurrentDevicePermissions = (name: PermissionName): PermissionStatus | undefined => {
  const [permission, setPermission] = useState<PermissionStatus>();

  useEffect(() => {
    navigator.permissions.query({ name }).then(setPermission);
  }, [permission]);

  return permission;
};

and it's being used like this:

const cameraPermissionState = useCurrentDevicePermissions('camera' as PermissionName);

Which works, and will update when the permissions update. However, when I log this out, I get a bunch of logs (presumably because it's constantly checking for new permissions):

permissions screenshot w/ console.log

I've tried useMemo but I'm not entirely sure if that's the solution. How do I render this hook once soo it'll look something like

{
  name: 'video_capture', state: 'prompt', onchange: null
} // log once

and once granted or denied

{
  name: 'video_capture', state: 'granted', onchange: null
} // log once

CodePudding user response:

You've created a loop using permission as a dependency if the useEffect block. When the permission changes, useEffect triggers, and sets the permission state with a new object causing useEffect to trigger...

Since the useEffect uses the name parameter in the callback, and you want it to trigger when the name changes use it as the dependency.

If you want to track permission status changes, register to the change event of the PermissionStatus object.

Example:

const { useState, useEffect } = React;

const useCurrentDevicePermissions = (name) => {
  const [permission, setPermission] = useState(null);
  
  useEffect(() => {
    navigator.permissions.query({ name }).then(setPermission);
  }, [name]);

  useEffect(() => {
    if(!permission) return;
    
    const changeHandler = e => setPermission(e.target);
    
    permission.addEventListener('change', changeHandler);
  
    return () => {
      permission.removeEventListener('change', changeHandler);
    };
  }, [permission]);
    
  return permission;
};

const Demo = () => {
  const permission = useCurrentDevicePermissions('notifications');
  
  if(!permission) return null;
  
  return (
    <div>{permission.name} is {permission.state}</div>
  );
};

ReactDOM
  .createRoot(root)
  .render(<Demo />);
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>

<div id="root"></div>

  • Related