SWR - An awesome react hooks for data fetching
Tags: javascript, react, vercel, node
Published: 2021-12-28 13:46:09

Data fetching is an integral part of every application we build. In modern web development we deal with a lot of data-fetching mechanisms to fetch data from a web server. We generally store this data in a state of our application. 

The question would arise: what happens when we refresh the page? The data should be repeatedly retrieved or persisted if it is not then we would definitely get a blank page. This is usually handled in our application with the API calls inside our useEffect() hook, componentDidMount() or write custom fetch Hook instead.

In this article we will learn about [useSWR] (https://swr.vercel.app/) hook which is a library we can use that handles all heavy lifting tasks for us not just data fetching but even data revalidation, caching, data pagination, page focus, data refresh, realtime, handling errors and lot more. 

We will compare the use of useSWR react hook and without the use of useSWR hook and see how our application behaves differently.

#What is SWR?

[SWR](https://swr.vercel.app/) is a React Hooks, a fast and lightweight library built by Vercel, the name comes from the term [stale-while-revalidate](https://datatracker.ietf.org/doc/html/rfc5861). A HTTP cache invalidation strategy popularized by HTTP RFC 5861. SWR is a strategy to first return the data from cache (stale), then send the fetch request (revalidate), and finally come with the up-to-date data. 

SWR’s useSWR(key, fetcher, options) is a Hook that retrieves data asynchronously from a URL with the aid of a fetcher function, both passed as arguments to the Hook. The key argument here is the URL in string format, and the fetcher is either a function declared in the global configuration, a predefined custom function, or a function defined as the useSWR() argument.

By default, useSWR() returns the data received, a validation request state, a manual revalidate argument, and an error, if there are any. This can be easily done by setting the Hook to a destructurable object variable:

```shell
const { data, isValidating, revalidate, error } = useSWR(key, fetcher)
```

We will definitely take a look into the arguments it takes in our demo application here. In this tutorial we will only focus on **stale-while revalidate** feature useSWR provides us. We will see the difference in normal fetch/axios API calls and implementation of useSWR hook.


#Project Installation

Let’s create a simple next project and give it a name of useswr-demo. 

```shell
npx create-next-app useswr-demo
```

After project creation we will spin up a local server by connecting a database with MongoDB Atlas and create a fresh cluster there. We will grab the connection string from the MongoDB and paste it inside of our .env.local file. We can also check the .env.example file for reference.

Let’s install vercel/node and mongodb with the command below.

```shell
npm i mongodb @vercel/node
```

We will now head into our api directory and create a new folder called lib. Inside there we will create a database.js file where we will add some function to connect with our mongoDB.

```javascript
const MongoClient = require("mongodb").MongoClient;
let cachedDb = null;

export const connectToDatabase = async () => {
  if (cachedDb) {
    console.log("Using existing DB connection");
    return Promise.resolve(cachedDb);
  }

  return MongoClient.connect(process.env.MONGODB_URI, {
    native_parser: true,
    useUnifiedTopology: true,
  })
    .then((client) => {
      let db = client.db("truskin-storage"); // free version
      console.log("New DB Connection");
      cachedDb = db;
      return cachedDb;
    })
    .catch((error) => {
      console.log("Mongo connect Error");
      console.log(error);
    });
};
```

We have now created a connection function we can readily use inside of our application. Lets create a new file and call it todo.js inside of api folder. Inside there we will import our connectTODatabase function we exported before. For the demo purpose we will just add two endpoints to achieve this GET and POST just to add simplicity 

```javascript
// Import Dependencies
import { connectToDatabase } from '../lib/database';

module.exports = async (req, res) => {
    const db = await connectToDatabase();
  if (req.method === 'GET') {
    const collection = await db.collection('todos');
    const todos = await collection.find({}).toArray();
    res.status(200).json({ todos });
  } else if (req.method === 'POST') {
    const newtodo = req.body;
    const collection = await db.collection('todos');
    const todos = await collection.insertOne(newtodo);
    res.status(200).json({ todos, status: 'API called sucessfully' });
  } else {
    res.status(404).json({ status: 'Error route not found' });
  }
};
```

Lastly before we can use our endpoints we created we will need to create a vercel.json file to make it work smooth

```shell
{
    "env": {
      "MONGODB_URI": "@mongodb-ur"
    },
    "headers": [
      {
        "source": "/(.*)",
        "headers": [
          {
            "key": "Access-Control-Allow-Origin",
            "value": "*"
          }
        ]
      }
    ]
  }
```

Now if we visit our route **http://localhost:3000/api/todos**
we must see an empty array returned to us. Currently we do not have any todos added.

#Without useSWR

We will start off by making the use of index.js file inside of our api folder. Lets first install axios for making our API calls.

```shell
npm i axios
```

We can import the Axios library and make a normal API call inside of our application.

```javascript
import Head from 'next/head';
import Image from 'next/image';

import axios from 'axios';
import styles from '../styles/Home.module.css';

export default function Index(props) {
  const todoList = props.data;
  return (
    <div className={styles.container}>
      <Head>
       ...
      </Head>

      <main className={styles.main}>
        <ul>
          {todoList.map((todo, index) => (
            <li key={index}>
              <a>{todo.task}</a>
            </li>
          ))}
        </ul>
      </main>

      <footer className={styles.footer}>
       ...
      </footer>
    </div>
  );
}

export const getStaticProps = async () => {
  const res = await axios.get('http://localhost:3000/api/todos');
  return {
    props: { data: res.data.todos },
  };
};
```

This is a simple way to make a call to our API. For demo purpose I will bring in Postman and send a POST request to our endpoint 

```shell
 http://localhost:3000/api/todos
```


![Postman Screenshot](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v1qzwtk36nm4u5kbhxxe.png)

We will get a success status and we can see the reflection inside of our MongoDB collection. Let’s simulate the changes in the database by manually deleting a document. If we come back to our application

```shell
http://localhost:3000
```

We won't see any changes unless we refresh the page. Well this is the main concept we are trying to look into: how can we revalidate the stale data and update our UI. Let’s solve this problem by implementing the useSWR hook.

#With useSWR

Let’s first install our library with the following command. We will make the use of both useSWR and axios to see this in action. But we can simply achieve this with just useSWR only. 

```shell
npm install swr 
```

We will create a new file todo.js inside of pages and do the same we did before but with useSWR library which looks something like this.

```javascript
import axios from 'axios';
import useSWR from 'swr';

import styles from '../styles/Home.module.css';

export default function Users() {
  const address = 'http://localhost:3000/api/todos';
  const fetcher = async (url) =>
    await axios.get(url).then((res) => res.data.todos);
  const { data, error } = useSWR(address, fetcher, {
    revalidateOnFocus: true, // auto revalidate when the window is focused 
  });

  if (error) <p>Loading failed...</p>;
  if (!data) <h1>Loading...</h1>;

  return (
    <div>
      <main className={styles.main}>
        <div className="container">
          {data && (
            <ul>
              {data.map((todo, index) => (
                <li key={index}>
                  <a>{todo.task}</a>
                </li>
              ))}
            </ul>
          )}
        </div>
      </main>
    </div>
  );
}

```
For demo purpose I will bring in Postman and test a POST request to our endpoint 

```shell
 http://localhost:3000/api/todos
```

We will get a success status and we can see the reflection inside of our MongoDB collection. Let’s simulate the changes in the database by manually deleting a document. If we come back to our application 

```shell
http://localhost:3000/todos
```
We can now see our UI has been updated with fresh data. We did not have to refresh the page to re-fetch the new data. And, we’ve done it! There we have a very barebones example of using SWR with Axios to update our stale data inside of our UI.

**<u>Note:</u>** We can change the cached data for /todos by calling mutate(newData). However, if we just run mutate(), it will refresh the data for /todos in the background. mutate knows to request the /todos endpoint again, since that’s where the mutate function came from.

![Screen recording differences](https://media.giphy.com/media/cio5qkigBSrnL2w3hF/giphy.gif)

##Other features provided

- Refetch on Interval
- Local Mutation
- Dependent fetching
- Smart error retry
- Scroll Position Recovery
- Dependent fetching
- Supports fetching from both REST and GraphQL APIs
- Typescript and React Native ready
- Pagination

#Conclusion

In conclusion, useSWR Hook can be a great choice for data fetching in React. I hope this article has provided us some insight into fetching data in Next.js applications with useSWR. We just scratched the surface. We still have a lot more features this library offers us with. Its caching, pagination and auto refetching can enhance user experience. Moreover, it's lightweight and backend agnostic, which allows fetching data from any kind of APIs or databases quickly and easily.

Thanks for reading. Please find the Github repo in the link [here](https://github.com/ishan-me/useswr-demo).
 
Please refer to the official [documentation](https://swr.vercel.app/) for more details.

Happy coding!