DEV Community

Cover image for SWR - An awesome react hooks for data fetching
Ishan Manandhar
Ishan Manandhar

Posted on

SWR - An awesome react hooks for data fetching

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 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 is a React Hooks, a fast and lightweight library built by Vercel, the name comes from the term stale-while-revalidate. 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:

const { data, isValidating, revalidate, error } = useSWR(key, fetcher)
Enter fullscreen mode Exit fullscreen mode

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.

npx create-next-app useswr-demo
Enter fullscreen mode Exit fullscreen mode

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.

npm i mongodb @vercel/node
Enter fullscreen mode Exit fullscreen mode

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.

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);
    });
};
Enter fullscreen mode Exit fullscreen mode

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

// 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' });
  }
};
Enter fullscreen mode Exit fullscreen mode

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

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

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.

npm i axios
Enter fullscreen mode Exit fullscreen mode

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

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 },
  };
};
Enter fullscreen mode Exit fullscreen mode

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

 http://localhost:3000/api/todos
Enter fullscreen mode Exit fullscreen mode

Postman Screenshot

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

http://localhost:3000
Enter fullscreen mode Exit fullscreen mode

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.

npm install swr 
Enter fullscreen mode Exit fullscreen mode

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.

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>
  );
}

Enter fullscreen mode Exit fullscreen mode

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

 http://localhost:3000/api/todos
Enter fullscreen mode Exit fullscreen mode

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

http://localhost:3000/todos
Enter fullscreen mode Exit fullscreen mode

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.

Note: 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

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.

Please refer to the official documentation for more details.

Happy coding!

Discussion (2)

Collapse
thomassalty profile image
Thomas Soos

Hi,

I have zero experience with mongodb and swr and I tried following every step in this article but I kept getting 404 errors, both in the browser and in postman.
You may have done other steps that you forgot to include here?

I even cloned the repo from your github and ran npm install to install the dependencies and ran npm run dev which resulted in 500 errors, both in the browser and in postman. Here are a few screenshots:

http://localhost:3000

http://localhost:3000/api/todos

Have you tried starting from zero and just following the steps in this article?

Also can you please add a little bit of explanation for this line?

let db = client.db("truskin-storage"); // free version
Enter fullscreen mode Exit fullscreen mode
Collapse
ishanme profile image
Ishan Manandhar Author

I started the demo of SWR from scratch. Currently, the database I used has expired due to inactivity, but you can generate your API key and set a .env file. You can generate it by creating a new cluster in MongoDB Atlas.

let db = client.db("truskin-storage");

The string "truskin-storage" is the DB kind of alias name generated by Mongo during the creation of the database itself.