Search code examples
javascriptreactjsprogressive-web-appsservice-worker

Offline Pages with Service-Worker React


I am totally new to service-workers! I am having a react site running in the localhost and about to be deployed. I am following the code specified here.

const isLocalhost = Boolean(
window.location.hostname === 'localhost' ||
    // [::1] is the IPv6 localhost address.
    window.location.hostname === '[::1]' ||
    // 127.0.0.1/8 is considered localhost for IPv4.
    window.location.hostname.match(
      /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
    )
)

export default function register () {
  if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
    // The URL constructor is available in all browsers that support SW.
    const publicUrl = new URL(process.env.PUBLIC_URL, window.location)
    if (publicUrl.origin !== window.location.origin) {
      // Our service worker won't work if PUBLIC_URL is on a different origin
      // from what our page is served on. This might happen if a CDN is used to
      // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
      return
    }

    window.addEventListener('load', () => {
      const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`

      if (!isLocalhost) {
        // Is not local host. Just register service worker
        registerValidSW(swUrl)
      } else {
        // This is running on localhost. Lets check if a service worker still exists or not.
        checkValidServiceWorker(swUrl)
      }
    })
  }
}

function registerValidSW (swUrl) {
  navigator.serviceWorker
    .register(swUrl)
    .then(registration => {
      registration.onupdatefound = () => {
        const installingWorker = registration.installing
        installingWorker.onstatechange = () => {
          if (installingWorker.state === 'installed') {
            if (navigator.serviceWorker.controller) {
              // At this point, the old content will have been purged and
              // the fresh content will have been added to the cache.
              // It's the perfect time to display a "New content is
              // available; please refresh." message in your web app.
              console.log('New content is available; please refresh.')
            } else {
              // At this point, everything has been precached.
              // It's the perfect time to display a
              // "Content is cached for offline use." message.
              console.log('Content is cached for offline use.')
            }
          }
        }
      }
    })
    .catch(error => {
      console.error('Error during service worker registration:', error)
    })
}

function checkValidServiceWorker (swUrl) {
  // Check if the service worker can be found. If it can't reload the page.
  fetch(swUrl)
    .then(response => {
      // Ensure service worker exists, and that we really are getting a JS file.
      if (
        response.status === 404 ||
        response.headers.get('content-type').indexOf('javascript') === -1
      ) {
        // No service worker found. Probably a different app. Reload the page.
        navigator.serviceWorker.ready.then(registration => {
          registration.unregister().then(() => {
            window.location.reload()
          })
        })
      } else {
        // Service worker found. Proceed as normal.
        registerValidSW(swUrl)
      }
    })
    .catch(() => {
      console.log(
        'No internet connection found. App is running in offline mode.'
      )
    })
}

export function unregister () {
  if ('serviceWorker' in navigator) {
    navigator.serviceWorker.ready.then(registration => {
      registration.unregister()
    })
  }
}

This only works in Production but, I want the offline-site to run in the localhost and also cache only the base route ie /. I don't want to cache any other pages.
Any help is greatly appreciated !
Thanks!


Solution

  • Here's what you could do.

    //this is your settings component
    export default function Settings(props) {
      const [offline, setOffline] = useState(false);
      useEffect(() => {
        if(!navigator.onLine) setOffline(true);
      }, []);
      return (
        <>
        {offline && (
           <div>You are offline please connect to the internet</div>
           <button onClick={() => window.location.reload(true)}>Refresh</button>
        )}
        {!offline && (
          <div>This is my actual settings page</div>
        )}
        </>
      );
    }
    

    There you go, you have conditional rendering of your settings, on the basis of the user being offline. Or you could also try alerting the user when the fetch fails(the user is offline). Like below:

    export default function Settings(props) {
      const handleAPICall = () => {
        fetch(YOUR_API_ENDPOINT).then(res => res.json()).then(data => useData(data)).catch(err => {
          setOffline(true);
        });
      }
      return (
        <>
         {offline && (
           <Alert timeout={'3s'}>You are offline please connect to the internet</Alert>
         )}
         <div>
            <p>This is my actual settings page</p>
            <button onClick={handleAPICall}>Fetch data</button>
         </div>
        </>
      );
        </>
      );
    }
    

    I hope you find one of these approaches applicable to you. See, making only a part of your web app offline was never the right approach to your problem in the first place. Sometimes it gets hard to ask the right questions because you yourself don't know what you want. But we have to keep grinding.