Create react app version check
Tags: react, version
Published: 2021-07-01 03:30:34

The [create react app](https://reactjs.org/docs/create-a-new-react-app.html) is a great tool to bootstrap any new project you're working on. They bundle a bunch of useful tool chains in to one single package so you can hit the ground running. Here are some of the things it provides out of the box.


- React, JSX, ES6, TypeScript and Flow syntax support.
- Language extras beyond ES6 like the object spread operator.
- Autoprefixed CSS, so you don’t need -webkit- or other prefixes.
- A fast interactive unit test runner with built-in support for coverage reporting.
- A live development server that warns about common mistakes.
- A build script to bundle JS, CSS, and images for production, with hashes and sourcemaps.
- An offline-first service worker and a web app manifest, meeting all the Progressive Web App criteria. (Note: Using the service worker is opt-in as of react-scripts@2.0.0 and higher)
- Hassle-free updates for the above tools with a single dependency.

With this you can add something like [react-router](https://reactrouter.com/) and you have the bones for a new SPA (single page application).

That's all great but since it's a single page application, how do people using the site know that there's a newer version available? This is especially important if you have updated API contracts in a deployment. 

 Unless you have a defined pipeline to do this already I've got a cheap and easy way to inform your users that they may need to refresh the page to get the latest changes.

The create-react-app creates a manifest.json file when the `yarn build` command is run. This file essentially tells the application where/what files exist. The file names are hashed for each build. This means we can tell if something has changed, as long as we poll this manifest file somehow...

So we need to create a component of sorts that can sit at a high level, it needs to be responsible for polling this manifest and telling the UI if there has been a change.

Here's an example I wrote, using material UI to display a snackbar whenever the version had changed. 

```
import {Button} from '@material-ui/core';
import {CloseOutlined} from '@material-ui/icons';
import {useSnackbar} from 'notistack';
import React, {useState} from 'react';

const MANIFEST = '/asset-manifest.json';
const POLL_INTERVAL = 60000;

export const VersionCheck: React.FC = ({children}) => {
  const {enqueueSnackbar, closeSnackbar} = useSnackbar();
  const [dismissedVersion, setDismissedVersion] = useState('');

  React.useEffect(() => {
    const getLatestVersion = async () => {
      const response = await fetch(MANIFEST);
      return await response.text();
    };

    const init = async () => {
      try {
        const latestVersion = await getLatestVersion();
        localStorage.setItem('tend-version', latestVersion);
      } catch (ex) {
        // log to sentry / or something
      } finally {
        setTimeout(poll, POLL_INTERVAL);
      }
    };

    const poll = async () => {
      try {
        const currentVersion = localStorage.getItem('tend-version');
        const latestVersion = await getLatestVersion();

        if (currentVersion && currentVersion !== latestVersion && latestVersion !== dismissedVersion) {
          enqueueSnackbar('A new version is available', {
            variant: 'info',
            persist: true,
            preventDuplicate: true,
            action: (key) => (
              <>
                <Button color="inherit" onClick={() => window.location.reload()}>
                  Refresh
                </Button>
                <Button
                  color="inherit"
                  variant="text"
                  onClick={() => {
                    setDismissedVersion(latestVersion);
                    closeSnackbar(key);
                  }}>
                  <CloseOutlined />
                </Button>
              </>
            ),
          });
        }
      } catch (ex) {
        // log somewhere
      } finally {
        setTimeout(poll, POLL_INTERVAL);
      }
    };

    if (process.env.NODE_ENV === 'production') {
      init();
    }
  }, [closeSnackbar, dismissedVersion, enqueueSnackbar]);

  return <>{children}</>;
};
```

This would display the following snackbar when the deployed files had changed. 

![alt text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ixd9n2cv548v6ms36kc8.png).

Why would you want this? With a SPA there's sometimes no need at all to refresh the website to get the latest files. This means clients consuming your APIs can potentially have very out of date code running. If you have made key contract changes between your API and clients you'll need to tell them somehow. This can potentially short circuit bug reports about recently touched parts of the UI not working correctly.