PWA Custom Install App Button not being displayed even though the install button is present next to the search bar

113 Views Asked by At

The install button is visible next to Google Chrome's search bar. Service workers are properly registered and the manifest.json file has all the required properties defined. This is confirmed by the PWA report from the Lighthouse tool where there are no issues whatsoever.

The problem: My custom PWA install button is not being displayed because the beforeInstallPrompt doesn't seem to be firing consistently - on rare occasions I can see the button displayed.

This is my button logic:

import { useEffect, useState } from 'react';
import usePWAInstallPrompt from 'hooks/usePWAInstallPrompt/usePWAInstallPrompt';
import { t } from 'i18next';
import { IconShareIOS } from 'components/icons';
import classNames from 'classnames';

const ICON_HEIGHT = 20;

type PWAInstallSidebarButtonProps = {
  buttonStyle?: string;
};

function PWAInstallSidebarButton({
  buttonStyle,
}: PWAInstallSidebarButtonProps) {
  const [prompt, promptToInstall] = usePWAInstallPrompt();
  const [isVisible, toggleVisible] = useState(false);

  const userAgent = window.navigator.userAgent.toLowerCase();
  const isFirefox = userAgent.includes('firefox');
  const isSafari =
    userAgent.includes('safari') && !userAgent.includes('chrome');

  const handleClick = () => {
    promptToInstall();
    toggleVisible(false);
  };

  useEffect(() => {
    if (prompt) {
      toggleVisible(true);
    }
  }, [prompt]);

  if (isFirefox || isSafari) {
    return null;
  }

  return (
    <li
      className={classNames({
        invisible: !isVisible,
      })}
    >
      <button onClick={handleClick} className={buttonStyle}>
        <IconShareIOS className="mr-3" height={ICON_HEIGHT} />
        {t('Add to home screen')}
      </button>
    </li>
  );
}

export default PWAInstallSidebarButton;

This is my usePWAInstallPrompt.ts hook

import { useState, useEffect } from 'react';
import checkMediaProperty from 'utils/browser/checkMediaProperty';


declare global {
  interface WindowEventMap {
    beforeinstallprompt: BeforeInstallPromptEvent;
  }
}

interface BeforeInstallPromptEvent extends Event {
  readonly platforms: string[];
  readonly userChoice: Promise<{
    outcome: 'accepted' | 'dismissed';
    platform: string;
  }>;
  prompt(): Promise<void>;
}

const DISPLAY_STANDALONE = 'display-mode: standalone';

export default function usePWAInstallPrompt(): [
  BeforeInstallPromptEvent | null,
  () => void
] {
  const [prompt, setState] = useState<BeforeInstallPromptEvent | null>(null);
  const promptToInstall = () => {
    if (prompt) {
      return prompt.prompt();
    }
    return Promise.reject(
      new Error(
        'Attempted installing before browser sent "beforeinstallprompt" event!'
      )
    );
  };

  useEffect(() => {
    const isAppInStandalone = checkMediaProperty(DISPLAY_STANDALONE);

    if (!isAppInStandalone){
      const ready = (e: BeforeInstallPromptEvent) => {
        e.preventDefault();
        setState(e);
      };
      window.addEventListener('beforeinstallprompt', ready);
      return () => {
        window.removeEventListener('beforeinstallprompt', ready);
      };
    }
  }, []);

  return [prompt, promptToInstall];
}

And this is the checkMediaQuery.ts utility function:

import getWindowProperty from "utils/browser/getWindowProperty";

const checkMediaProperty = (mediaQueryString: string): boolean => {
  const { matchMedia } = getWindowProperty();

  return (matchMedia && matchMedia(`(${mediaQueryString})`).matches) || false;
};

export default checkMediaProperty;

I consulted numerous resources including but not limited to books on the subject, google search, stackoverflow and reddit threads with similar questions.

It looks like something is escaping me, since no matter what I try, the result remains the same.

0

There are 0 best solutions below