Data fetching lies at the heart of any robust web application, and React paired with Next.js offers powerful tools for efficiently managing this process. In this guide, we'll dive into real-world examples of fetching, caching, and revalidating data to help you master these crucial concepts.
1. Fetching Data on the Server: A Practical Example
Let's consider a scenario where we need to fetch user data from an API on the server side using Next.js. We'll use the native fetch
API extended by Next.js to configure caching and revalidation behavior.
// pages/user/[id].tsx
import { useRouter } from 'next/router';
const User = ({ user }) => {
// Access user data fetched on the server
return (
<div>
<h1>{user.name}</h1>
{/* Display other user details */}
</div>
);
};
export async function getServerSideProps({ params }) {
const res = await fetch(`https://api.example.com/users/${params.id}`);
const user = await res.json();
if (!user) {
return {
notFound: true,
};
}
return {
props: { user }, // Will be passed to the page component as props
};
}
export default User;
In this example:
We use
getServerSideProps
to fetch user data on the server.The
fetch
request is configured to handle caching and revalidation by Next.js.
2. Caching Data: Optimize Performance
Caching is crucial for preventing unnecessary data refetching. Let's explore a scenario where we want to cache a list of articles fetched from an API.
// pages/articles.tsx
import { useEffect, useState } from 'react';
const Articles = () => {
const [articles, setArticles] = useState([]);
useEffect(() => {
const fetchArticles = async () => {
const cachedArticles = localStorage.getItem('cachedArticles');
if (cachedArticles) {
// Use cached data if available
setArticles(JSON.parse(cachedArticles));
}
// Fetch fresh data and update the cache
const res = await fetch('https://api.example.com/articles');
const newArticles = await res.json();
setArticles(newArticles);
localStorage.setItem('cachedArticles', JSON.stringify(newArticles));
};
fetchArticles();
}, []);
return (
<div>
<h1>Latest Articles</h1>
{/* Render the list of articles */}
</div>
);
};
export default Articles;
Key points:
We use
localStorage
to cache articles locally.The component first checks for cached data before making a fresh API request.
3. Revalidating Data: Keeping it Fresh
Revalidating is essential when dealing with frequently changing data. Let's explore time-based revalidation in a scenario where we fetch stock prices.
// pages/stocks.tsx
import { useEffect, useState } from 'react';
const Stocks = () => {
const [stockPrices, setStockPrices] = useState([]);
useEffect(() => {
const fetchStockPrices = async () => {
const res = await fetch('https://api.example.com/stocks', { next: { revalidate: 300 } });
const newPrices = await res.json();
setStockPrices(newPrices);
};
fetchStockPrices();
}, []);
return (
<div>
<h1>Stock Prices</h1>
{/* Render the stock prices */}
</div>
);
};
export default Stocks;
Here:
We use the
next.revalidate
option to set a 5-minute cache lifetime.The data will automatically revalidate after the specified time.
4. Opting Out of Data Caching: Fine-tuning Fetch Requests
In certain scenarios, you might need to opt out of caching. Let's explore a case where we fetch real-time data for a live chat application.
// components/ChatMessages.tsx
import { useEffect, useState } from 'react';
const ChatMessages = () => {
const [messages, setMessages] = useState([]);
useEffect(() => {
const fetchChatMessages = async () => {
const res = await fetch('https://api.example.com/messages', { cache: 'no-store' });
const newMessages = await res.json();
setMessages(newMessages);
};
fetchChatMessages();
}, []);
return (
<div>
{/* Render the chat messages */}
</div>
);
};
export default ChatMessages;
Key considerations:
- We use
cache: 'no-store'
to ensure real-time data fetching on every request.
5. Fetching Data on the Client: Leveraging Axios
For client-side data fetching, let's explore using the Axios library instead of SWR to fetch and cache GitHub repositories.
// pages/github-repos.tsx
import axios from 'axios';
import { useEffect, useState } from 'react';
const GitHubRepos = () => {
const [repos, setRepos] = useState([]);
const [error, setError] = useState(null);
useEffect(() => {
const fetchGitHubRepos = async () => {
try {
const response = await axios.get('https://api.github.com/users/octocat/repos');
setRepos(response.data);
} catch (err) {
setError('Error fetching data');
}
};
fetchGitHubRepos();
}, []);
return (
<div>
<h1>GitHub Repositories</h1>
{error ? <div>{error}</div> : /* Render the list of repositories */}
</div>
);
};
export default GitHubRepos;
In this updated example:
We replace the
useSWR
hook with theuseState
anduseEffect
hooks for manual data fetching.Axios is used to make the HTTP request, providing more control over the fetching process.
The fetched data is stored in the
repos
state, and any errors are captured in theerror
state.
By making these changes, you have the flexibility to choose the data fetching library that best fits your needs, and in this case, Axios provides a robust and widely-used alternative to SWR.