Skip to content
On this page

SolidJS

You can use the built-in Vite virtual module virtual:pwa-register/solid for SolidJS which will return createSignal stateful values (createSignal<boolean>) for offlineReady and needRefresh.

WARNING

You will need to add workbox-window as a dev dependency to your Vite project.

Type declarations

TIP

If your TypeScript build step or IDE complain about not being able to find modules or type definitions on imports, add the following to the compilerOptions.types array of your tsconfig.json:

json
{
  "compilerOptions": {
    "types": [
      "vite-plugin-pwa/client"
    ]
  }
}
{
  "compilerOptions": {
    "types": [
      "vite-plugin-pwa/client"
    ]
  }
}

Or you can add the following reference in any of your d.ts files (for example, in vite-env.d.ts or global.d.ts):

ts
/// <reference types="vite-plugin-pwa/client" />
/// <reference types="vite-plugin-pwa/client" />

From version 0.14.5 you can also use types definition for solid instead of vite-plugin-pwa/client:

json
{
  "compilerOptions": {
    "types": [
      "vite-plugin-pwa/solid"
    ]
  }
}
{
  "compilerOptions": {
    "types": [
      "vite-plugin-pwa/solid"
    ]
  }
}

Or you can add the following reference in any of your d.ts files (for example, in vite-env.d.ts or global.d.ts):

ts
/// <reference types="vite-plugin-pwa/solid" />
/// <reference types="vite-plugin-pwa/solid" />
ts
declare module 'virtual:pwa-register/solid' {
  // eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error
  // @ts-expect-error ignore when solid-js is not installed
  import type { Accessor, Setter } from 'solid-js'
  import type { RegisterSWOptions } from 'vite-plugin-pwa/types'

  export type { RegisterSWOptions }

  export function useRegisterSW(options?: RegisterSWOptions): {
    needRefresh: [Accessor<boolean>, Setter<boolean>]
    offlineReady: [Accessor<boolean>, Setter<boolean>]
    updateServiceWorker: (reloadPage?: boolean) => Promise<void>
  }
}
declare module 'virtual:pwa-register/solid' {
  // eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error
  // @ts-expect-error ignore when solid-js is not installed
  import type { Accessor, Setter } from 'solid-js'
  import type { RegisterSWOptions } from 'vite-plugin-pwa/types'

  export type { RegisterSWOptions }

  export function useRegisterSW(options?: RegisterSWOptions): {
    needRefresh: [Accessor<boolean>, Setter<boolean>]
    offlineReady: [Accessor<boolean>, Setter<boolean>]
    updateServiceWorker: (reloadPage?: boolean) => Promise<void>
  }
}

Prompt for update

You can use this ReloadPrompt.tsx component:

ReloadPrompt.tsx
tsx
import type { Component } from 'solid-js'
import { Show } from 'solid-js'
import { useRegisterSW } from 'virtual:pwa-register/solid'
import styles from './ReloadPrompt.module.css'

const ReloadPrompt: Component = () => {
  const {
    offlineReady: [offlineReady, setOfflineReady],
    needRefresh: [needRefresh, setNeedRefresh],
    updateServiceWorker,
  } = useRegisterSW({
    onRegistered(r) {
      // eslint-disable-next-line prefer-template
      console.log('SW Registered: ' + r)
    },
    onRegisterError(error) {
      console.log('SW registration error', error)
    },
  })

  const close = () => {
    setOfflineReady(false)
    setNeedRefresh(false)
  }

  return (
    <div className={styles.Container}>
      <Show when={offlineReady() || needRefresh()}>
        <div className={styles.Toast}>
          <div className={styles.Message}>
            <Show
              fallback={<span>New content available, click on reload button to update.</span>}
              when={offlineReady()}
            >
              <span>App ready to work offline</span>
            </Show>
          </div>
          <Show when={needRefresh()}>
            <button className={styles.ToastButton} onClick={() => updateServiceWorker(true)}>Reload</button>
          </Show>
          <button className={styles.ToastButton} onClick={() => close()}>Close</button>
        </div>
      </Show>
    </div>
  )
}

export default ReloadPrompt
import type { Component } from 'solid-js'
import { Show } from 'solid-js'
import { useRegisterSW } from 'virtual:pwa-register/solid'
import styles from './ReloadPrompt.module.css'

const ReloadPrompt: Component = () => {
  const {
    offlineReady: [offlineReady, setOfflineReady],
    needRefresh: [needRefresh, setNeedRefresh],
    updateServiceWorker,
  } = useRegisterSW({
    onRegistered(r) {
      // eslint-disable-next-line prefer-template
      console.log('SW Registered: ' + r)
    },
    onRegisterError(error) {
      console.log('SW registration error', error)
    },
  })

  const close = () => {
    setOfflineReady(false)
    setNeedRefresh(false)
  }

  return (
    <div className={styles.Container}>
      <Show when={offlineReady() || needRefresh()}>
        <div className={styles.Toast}>
          <div className={styles.Message}>
            <Show
              fallback={<span>New content available, click on reload button to update.</span>}
              when={offlineReady()}
            >
              <span>App ready to work offline</span>
            </Show>
          </div>
          <Show when={needRefresh()}>
            <button className={styles.ToastButton} onClick={() => updateServiceWorker(true)}>Reload</button>
          </Show>
          <button className={styles.ToastButton} onClick={() => close()}>Close</button>
        </div>
      </Show>
    </div>
  )
}

export default ReloadPrompt

and its corresponding ReloadPrompt.module.css styles module:

ReloadPrompt.module.css
css
.Container {
  padding: 0;
  margin: 0;
  width: 0;
  height: 0;
}
.Toast {
  position: fixed;
  right: 0;
  bottom: 0;
  margin: 16px;
  padding: 12px;
  border: 1px solid #8885;
  border-radius: 4px;
  z-index: 1;
  text-align: left;
  box-shadow: 3px 4px 5px 0 #8885;
  background-color: white;
}
.ToastMessage {
  margin-bottom: 8px;
}
.ToastButton {
  border: 1px solid #8885;
  outline: none;
  margin-right: 5px;
  border-radius: 2px;
  padding: 3px 10px;
}
.Container {
  padding: 0;
  margin: 0;
  width: 0;
  height: 0;
}
.Toast {
  position: fixed;
  right: 0;
  bottom: 0;
  margin: 16px;
  padding: 12px;
  border: 1px solid #8885;
  border-radius: 4px;
  z-index: 1;
  text-align: left;
  box-shadow: 3px 4px 5px 0 #8885;
  background-color: white;
}
.ToastMessage {
  margin-bottom: 8px;
}
.ToastButton {
  border: 1px solid #8885;
  outline: none;
  margin-right: 5px;
  border-radius: 2px;
  padding: 3px 10px;
}

Periodic SW Updates

As explained in Periodic Service Worker Updates, you can use this code to configure this behavior on your application with the virtual module virtual:pwa-register/solid:

ts
import { useRegisterSW } from 'virtual:pwa-register/solid'

const intervalMS = 60 * 60 * 1000

const updateServiceWorker = useRegisterSW({
  onRegistered(r) {
    r && setInterval(() => {
      r.update()
    }, intervalMS)
  }
})
import { useRegisterSW } from 'virtual:pwa-register/solid'

const intervalMS = 60 * 60 * 1000

const updateServiceWorker = useRegisterSW({
  onRegistered(r) {
    r && setInterval(() => {
      r.update()
    }, intervalMS)
  }
})

The interval must be in milliseconds, in the example above it is configured to check the service worker every hour.

WARNING

This only applies when importing any of the virtual modules or using workbox-window module.

Since workbox-window uses a time-based heuristic algorithm to handle service worker updates, if you build your service worker and register it again, if the time between last registration and the new one is less than 1 minute, then, workbox-window will handle the service worker update found event as an external event, and so the behavior could be strange (for example, if using prompt, instead showing the dialog for new content available, the ready to work offline dialog will be shown; if using autoUpdate, the ready to work offline dialog will be shown and shouldn't be shown).

Released under the MIT License.