Clear Cache on build for React Apps.
Tags: react, javascript, webdev
Published: 2020-07-16 05:51:09

## What problem are we trying to solve?
*Every time a build is created and deployed, the user has to do a hard refresh of the webpage to clear the cache to view the new changes done to the app. This is not a practical solution to ask the user to do it*

## Our Goal:
*I wanted a solution wherein every time I create a new build and deploy it to production, on a basic refresh of the page the user should be able to view the new changes.*

We will create a new app by using **create-react-app**. I will give the app name as clear-cache-app

```
npx create-react-app clear-cache-app
```
We will install `moment` library as well. You will understand its importance at a later stage.

```
cd clear-cache-app
npm i moment
```
Once all the packages are installed, test run the app once

```
npm start
```

In `package.json` file add the following code at the end of the file
```javascript
"buildDate": ""
```
Create a new file `update-build.js`. It should reside in the main folder besides `package.json`. 
`update-build.js` will have following code:

```javascript
const fs = require("fs");
const filePath = "./package.json";

const packageJson = JSON.parse(fs.readFileSync(filePath).toString());
packageJson.buildDate = new Date().getTime();

fs.writeFileSync(filePath, JSON.stringify(packageJson, null, 2));

const jsonData = {
  buildDate: packageJson.buildDate,
};

const jsonContent = JSON.stringify(jsonData);

fs.writeFile("./public/meta.json", jsonContent, "utf8", function (error) {
  if (error) {
    console.log("An error occured while saving build date and time to meta.json");
    return console.log(error);
  }

  console.log("Latest build date and time updated in meta.json file");
});

```
Whenever a new build is generated we will call this file. What we are doing in `update-build.js` is two main things:
1. We are generating a current date/time value in epoch.
2. We are updating that value in meta.json file. This file will be automatically generated every time a new build is created.

Now update your build command in `package.json` file as below:
```javascript
"build": "node ./update-build.js && react-scripts build",
```

Next, we will create a higher-order Component (HOC) called *withClearCache*. Our main *App* component will be passed as an argument to the *withClearCache*. The idea here is that before our content of App gets loaded into the browser we need to check whether the our content is latest or not.

We will create a new file into the `src` folder named `ClearCache.js` with the following code:

```javascript
import React, { useState, useEffect } from "react";
import packageJson from "../package.json";
import moment from "moment";

const buildDateGreaterThan = (latestDate, currentDate) => {
  const momLatestDateTime = moment(latestDate);
  const momCurrentDateTime = moment(currentDate);

  if (momLatestDateTime.isAfter(momCurrentDateTime)) {
    return true;
  } else {
    return false;
  }
};

function withClearCache(Component) {
  function ClearCacheComponent(props) {
    const [isLatestBuildDate, setIsLatestBuildDate] = useState(false);

    useEffect(() => {
      fetch("/meta.json")
        .then((response) => response.json())
        .then((meta) => {
          const latestVersionDate = meta.buildDate;
          const currentVersionDate = packageJson.buildDate;

          const shouldForceRefresh = buildDateGreaterThan(
            latestVersionDate,
            currentVersionDate
          );
          if (shouldForceRefresh) {
            setIsLatestBuildDate(false);
            refreshCacheAndReload();
          } else {
            setIsLatestBuildDate(true);
          }
        });
    }, []);

    const refreshCacheAndReload = () => {
      if (caches) {
        // Service worker cache should be cleared with caches.delete()
        caches.keys().then((names) => {
          for (const name of names) {
            caches.delete(name);
          }
        });
      }
      // delete browser cache and hard reload
      window.location.reload(true);
    };

    return (
      <React.Fragment>
        {isLatestBuildDate ? <Component {...props} /> : null}
      </React.Fragment>
    );
  }

  return ClearCacheComponent;
}

export default withClearCache;

```
Let's go through the code in the above file to see what exactly we are doing here:

* We are making an api call to `meta.json` file to access it's content. Browsers don't cache the api calls, so even if our files are cached we will always get the latest response from it. 
```javascript
   useEffect(() => {
      fetch("/meta.json")
        .then((response) => response.json())
        .then((meta) => {
          const latestVersionDate = meta.buildDate;
          const currentVersionDate = packageJson.buildDate;

          const shouldForceRefresh = buildDateGreaterThan(
            latestVersionDate,
            currentVersionDate
          );
          if (shouldForceRefresh) {
            setIsLatestBuildDate(false);
            refreshCacheAndReload();
          } else {
            setIsLatestBuildDate(true);
          }
        });
    }, []);
```
* As you can see above, in the response we get the build date from `meta.json` file. We pass that value to a function `buildDateGreaterThan` which accepts two arguments the latest build date generated by `meta.json` file and the cached build date which we took from `package.json` file. This function compares the two date/time values, it returns true if the latest build date is greater than the cached build date else false. We are using the `moment` library for date/time comparison.

```javascript
const buildDateGreaterThan = (latestDate, currentDate) => {
  const momLatestDateTime = moment(latestDate);
  const momCurrentDateTime = moment(currentDate);

  if (momLatestDateTime.isAfter(momCurrentDateTime)) {
    return true;
  } else {
    return false;
  }
};

```

* If the lastest build date is greater than the cached build date we will delete the service worker cache, browser cache and do a hard reload.

```javascript
const refreshCacheAndReload = () => {
      if (caches) {
        // Service worker cache should be cleared with caches.delete()
        caches.keys().then((names) => {
          for (const name of names) {
            caches.delete(name);
          }
        });
      }
      // delete browser cache and hard reload
      window.location.reload(true);
    };
```
* If the latest build date and the cached build date are same we don't clear the cache and we load the component.
```javascript
return (
      <React.Fragment>
        {isLatestBuildDate ? <Component {...props} /> : null}
      </React.Fragment>
    );
```

Now I want to display my build date and time. Since the build date generated is in epoch, I have created two utility functions that will help me format the date in *dd-mm-yyyy hh:mm*

```javascript
/**
 * Function returning the build date(as per provided epoch)
 * @param epoch Time in milliseconds
 */
export const getBuildDate = (epoch) => {
  const buildDate = moment(epoch).format("DD-MM-YYY HH:MM");
  return buildDate;
};
```

The final step is to call the Clear Cache Component in `App.js` and to display the build date in my UI. I have updated my `App.js` as following:

```javascript
import React from "react";
import logo from "./logo.svg";
import "./App.css";
import packageJson from "../package.json";
import { getBuildDate } from "./utils/utils";
import withClearCache from "./ClearCache";

const ClearCacheComponent = withClearCache(MainApp);

function App() {
  return <ClearCacheComponent />;
}

function MainApp(props) {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>Build date: {getBuildDate(packageJson.buildDate)}</p>
      </header>
    </div>
  );
}

export default App;

```
So that's it. You only need to run `npm run build` command and your build will be generated with new build date time. 

The complete source code can be found on my github repo
{% github https://github.com/ammartinwala52/clear-cache-react-app no-readme%}

> # Credit:
I want to give a shout out to Dinesh for his article on clearing the cache in react apps.{% link https://dev.to/flexdinesh/cache-busting-a-react-app-22lk %} I have taken the core idea of clearing the cache from his article. Do check it out.