47
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.
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)
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.
Let’s create a simple next project and give it a name of useswr-demo.
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.
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.
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
// 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
{
"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.
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
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 },
};
};
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
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
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.
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
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>
);
}
For demo purpose I will bring in Postman and test a POST request to our endpoint
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
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.
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.
- 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
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!
47