Install Button
Installation
Automatic
npx pwawtf add install-button
Manual Instructions
Register type definition overrides
Firstly, we are going to need to register the global Event
type definition overrides as the @types/react
type does not include the prompt
method.
Create a <root>/register.ts
file:
my-app/
├── ...
├── register.ts
├── ...
└── tsconfig.json
And add the following code:
// <root>/register.ts
declare global {
interface Event {
prompt: () => Promise<void>
}
}
export {};
Set up the InstallPromptEvent
Context
Next, we need to set up a React Context to store the event emitted by the "beforeinstallprompt"
event on page load.
Create a <root>/src/contexts/InstallPromptEvent.tsx
file:
my-app/
├── src/
│ └── contexts/
│ └── InstallPromptEvent.tsx
├── ...
├── register.ts
├── ...
└── tsconfig.json
And add the following code:
// <root>/contexts/InstallPromptEvent.tsx
import * as React from 'react'
// 1. Set up React Context for the global install prompt event.
const InstallPromptEventContext = React.createContext<Event | null>(null)
export function InstallPromptEventProvider({ children }: { children: React.ReactNode }) {
// 2. Set up state to store the event.
const [installPromptEvent, setInstallPromptEvent] = React.useState<Event | null>(
null,
)
// 3. Listen for any changes to the `"beforeinstallprompt"` event.
React.useEffect(() => {
window.addEventListener('beforeinstallprompt', setInstallPromptEvent)
return () =>
window.removeEventListener('beforeinstallprompt', setInstallPromptEvent)
}, [])
return (
// 4. Wrap the children in the context provider.
<InstallPromptEventContext.Provider value={installPromptEvent}>
{children}
</InstallPromptEventContext.Provider>
)
}
// 5. Export a hook to consume the context.
export function useInstallPromptEvent() {
return React.useContext(InstallPromptEventContext)
}
Set up the InstallButton
Component
After that, we need to set up a React component that will render a button to prompt the user to install the app.
Create a <root>/components/InstallButton.tsx
file:
my-app/
├── src/
│ ├── components/
│ │ └── InstallButton.tsx
│ └── contexts/
│ └── InstallPromptEvent.tsx
├── ...
├── register.ts
├── ...
└── tsconfig.json
And add the following code:
// <root>/components/InstallButton.tsx
import { useInstallPromptEvent } from './InstallPromptEvent'
export function InstallButton() {
// 1. Set up some state to show "Installing..." UI feedback.
const [installing, setInstalling] = React.useState(false)
// 2. Extract the install prompt event from context.
const event = useInstallPromptEvent()
// 3. Render the button if the app is ready to be installed
if (!event) return null
return (
<button
disabled={installing}
onClick={async () => {
try {
setInstalling(true)
await event.prompt()
} finally {
setInstalling(false)
}
}}
>
{installing ? 'Installing...' : 'Install App'}
</button>
)
}
Wire it together
Finally, we can wire it all together in our <root>/src/App.tsx
file.
my-app/
├── src/
│ ├── components/
│ │ └── InstallButton.tsx
│ ├── contexts/
│ │ └── InstallPromptEvent.tsx
| └── App.tsx
├── ...
├── register.ts
├── ...
└── tsconfig.json
// IMPORTANT: This component must be rendered at the root of your app.
import { InstallPromptEventProvider } from './InstallPromptEvent'
import { InstallButton } from './InstallButton'
function App() {
return (
<InstallPromptEventProvider>
<InstallButton />
</InstallPromptEventProvider>
)
}
Tada!
That's it! You should now have a working install button in your app like the one below:
Demo
TODO: stackblitz
Next Steps
Styling
TODO:
- Create styled implementations with Tailwind (https://web.dev/promote-install/ (opens in a new tab))
npx pwawtf add install-button --style top-banner
npx pwawtf add install-button --style bottom-banner
npx pwawtf add install-button --style button
npx pwawtf add install-button --style snackbar
iOS Fallback
Conditional Rendering
TODO:
- hide button when app is installed
- show fallback warning message when
onbeforeinstall
is not supported/doesn't get called