Next.js SWR is a powerful React Hooks library for data fetching, providing an efficient way to fetch and manage client-side data in your Next.js applications. SWR stands for Stale-While-Revalidate, a strategy that serves cached data while simultaneously fetching updated data, ensuring your app has fast and up-to-date data without unnecessary network calls.
The core philosophy of SWR revolves around the Stale-While-Revalidate concept. This means that when data is requested, SWR first returns the cached data (stale) while it revalidates in the background to fetch the latest data. This approach significantly enhances the user experience by reducing load times and ensuring that users see the most current information without delay. This method aligns with the HTTP cache invalidation strategy popularized by RFC 586.
SWR offers a multitude of features that make it an excellent choice for data fetching in client-side applications:
Automatic Caching and Revalidation: SWR caches data on the client side and automatically revalidates it, ensuring the app always displays the latest data. This mechanism helps prevent unnecessary request calls and reduces the server load.
Focus Tracking and Re-fetching: The library tracks when the user returns to a page and automatically re-fetches data to ensure accuracy, enhancing the reliability of displayed information.
Support for Multiple Data Sources: You can use any data-fetching library such as fetch, Axios, or even GraphQL, making it highly flexible and adaptable to various backend services.
Easy Error Handling and Data Prefetching: SWR provides built-in mechanisms for error handling and allows for data prefetching, which can be particularly useful in optimizing the user experience and ensuring smooth navigation across different parts of your app.
Infinite Loading and Pagination: The library supports advanced features like infinite loading, which is crucial for applications dealing with large datasets that need to be loaded progressively.
Global Configuration and Mutations: SWR allows global configuration settings and provides mutation APIs to manually update cached data, offering a high degree of control over data management.
The useSWR hook is the cornerstone of the SWR library. It simplifies data fetching in React applications by providing a declarative way to manage the loading, error, and data states. To start using the useSWR hook, you need to import it into your component. The useSWR hook accepts three parameters: a key (usually the API endpoint), a fetcher function, and an optional object of options that provides flexibility in handling the data. The useSWR hook returns an object containing several properties, including data, error, and isLoading.
Here’s a basic example of how to use useSWR: import useSWR from 'swr';
1import useSWR from 'swr'; 2 3const fetcher = (url) => fetch(url).then((res) => res.json()); 4 5function Profile() { 6 const { data, error } = useSWR('/api/user', fetcher); 7 8 if (error) return <div>Failed to load</div>; 9 if (!data) return <div>Loading...</div>; 10 return <div>Hello, {data.name}!</div>; 11}
In this example, useSWR accepts the URL /api/user as the key and the fetcher function. It returns data when the request is successful, handles errors, and shows a loading state while the data is being fetched.
A fetcher function is a critical part of using SWR. This function is responsible for fetching the data from a given source. It is an asynchronous function that returns the data you need. You can define the fetcher function according to the data source you're working with, whether it’s a REST API, GraphQL, or any other service.
For example, using the native fetch API, a simple fetcher function might look like this:
1const fetcher = (...args) => fetch(...args).then(res => res.json());
This function takes any number of arguments (...args), passes them to fetch, and then parses the response as JSON. If you're using a different library like Axios, the fetcher function would look slightly different:
1import axios from 'axios'; 2 3const fetcher = (url) => axios.get(url).then((res) => res.data);
In this case, the fetcher uses Axios to fetch data from the given URL and returns the response data.
SWR offers extensive configuration options to customize its behavior globally or locally within a component. The SWRConfig component is used to set global configuration, while individual hooks can also accept configuration options.
To apply global configurations, you wrap your application with the SWRConfig component:
1import { SWRConfig } from 'swr'; 2 3function MyApp({ Component, pageProps }) { 4 return ( 5 <SWRConfig value={{ fetcher }}> 6 <Component {...pageProps} /> 7 </SWRConfig> 8 ); 9}
In the above example, the global configuration sets a default fetcher function that all useSWR hooks will use unless overridden locally.
You can also configure SWR locally by passing an options object as the third parameter to useSWR:
1const { data, error } = useSWR('/api/user', fetcher, { 2 refreshInterval: 3000, 3 onErrorRetry: (error, key, config, revalidate, { retryCount }) => { 4 if (retryCount >= 10) return; 5 setTimeout(() => revalidate({ retryCount }), 5000); 6 } 7});
In this example, the data will refresh every 3000 milliseconds, and the onErrorRetry callback function will handle retries, limiting them to 10 attempts with a 5-second delay between retries.
Basic data loading with SWR, especially when fetching client-side data, is straightforward and efficient. You start by importing the useSWR hook and defining a fetcher function. The fetcher function is used to retrieve data from an API endpoint.
Here’s a simple example:
1import useSWR from 'swr'; 2 3const fetcher = (url) => fetch(url).then((res) => res.json()); 4 5function UserProfile() { 6 const { data, error, isLoading } = useSWR('/api/user', fetcher); 7 8 if (isLoading) return <div>Loading...</div>; 9 if (error) return <div>Error loading data</div>; 10 return <div>Hello, {data.name}!</div>; 11}
In this example, useSWR is used to fetch data from /api/user. The fetcher function retrieves and parses the JSON data. The hook returns data, error, and isLoading states to handle the different stages of the data fetching process.
SWR provides several advanced options to enhance your data fetching capabilities:
1const shouldFetch = true; 2const { data, error } = useSWR(shouldFetch ? '/api/data' : null, fetcher);
1const { data: user } = useSWR('/api/user', fetcher); 2const { data: profile } = useSWR(() => user ? `/api/profile/${user.id}` : null, fetcher);
1const { data, error, size, setSize } = useSWRInfinite( 2 (index) => `/api/data?page=${index + 1}`, 3 fetcher 4); 5 6const loadMore = () => setSize(size + 1);
1const { data, error } = useSWR('/api/data', fetcher, { refreshInterval: 5000 });
These advanced options make SWR a versatile tool for handling various data fetching scenarios, ensuring your application remains responsive and efficient.
Data revalidation is a core feature of SWR, ensuring that your application always displays the most accurate and up-to-date data. SWR supports several revalidation strategies:
1const { data, error } = useSWR('/api/data', fetcher, { revalidateOnFocus: true });
1const { data, error } = useSWR('/api/data', fetcher, { revalidateOnReconnect: true });
1import { mutate } from 'swr'; 2 3const handleSubmit = async () => { 4 await fetch('/api/update', { method: 'POST', body: JSON.stringify(newData) }); 5 mutate('/api/data'); 6};
1const { data, error } = useSWR('/api/data', fetcher, { refreshInterval: 3000 });
By using these revalidation strategies, SWR helps maintain data integrity and ensures your application reflects the latest data changes efficiently.
One of the key strengths of SWR is its built-in caching mechanism that ensures fast data retrieval. SWR caches data on the client side, which significantly reduces the number of network requests and speeds up data access.
Default Cache: By default, SWR uses a global cache shared across all useSWR hooks. This cache stores fetched data and reuses it whenever possible, reducing redundant network requests.
Custom Cache Providers: You can customize the cache provider to use more persistent storage solutions like localStorage or IndexedDB. This can be useful for storing larger amounts of data or for retaining data across page reloads.
1import { SWRConfig } from 'swr'; 2 3function App() { 4 return ( 5 <SWRConfig value={{ provider: () => new Map() }}> 6 <MyComponent /> 7 </SWRConfig> 8 ); 9}
1import { mutate } from 'swr'; 2 3const handleUpdate = async () => { 4 const updatedData = { ...data, name: 'New Name' }; 5 mutate('/api/user', updatedData, false); // Update local cache without revalidation 6};
SWR supports client-side data fetching, which is crucial for applications that require continuously updated information. Here are some strategies to achieve real-time data with SWR:
1const { data, error } = useSWR('/api/data', fetcher, { refreshInterval: 5000 });
1const { data, error } = useSWR('/api/data', fetcher, { 2 revalidateOnFocus: true, 3 revalidateOnReconnect: true, 4});
1useEffect(() => { 2 const socket = new WebSocket('wss://example.com/socket'); 3 4 socket.onmessage = (event) => { 5 const newData = JSON.parse(event.data); 6 mutate('/api/data', newData, false); // Update local cache without revalidation 7 }; 8 9 return () => socket.close(); 10}, []);
SWR promotes the creation of reusable data fetching hooks, which enhance code maintainability and readability. Additionally, SWR supports immutable data practices to prevent accidental modifications.
1function useUser(id) { 2 const { data, error, isLoading } = useSWR(`/api/user/${id}`, fetcher); 3 return { data, error, isLoading }; 4} 5 6function UserProfile({ id }) { 7 const { data, error, isLoading } = useUser(id); 8 9 if (isLoading) return <div>Loading...</div>; 10 if (error) return <div>Error loading data</div>; 11 return <div>Hello, {data.name}!</div>; 12}
1const handleUpdate = async () => { 2 const updatedData = { ...data, name: 'New Name' }; 3 mutate('/api/user', updatedData, false); // SWR ensures the updated data is treated as a new object 4};
To optimize performance in your Next.js application using SWR, it's crucial to prevent unnecessary network calls. This not only improves the user experience but also reduces server load and bandwidth usage.
1const shouldFetch = userId !== null; 2const { data, error } = useSWR(shouldFetch ? `/api/user/${userId}` : null, fetcher);
Request Deduplication: SWR automatically deduplicates requests for the same key within a short time frame. This prevents multiple components from making redundant requests.
Focus and Reconnect: By default, SWR revalidates data when the window gains focus or the network connection is restored. You can customize or disable this behavior to reduce network calls.
1const { data, error } = useSWR('/api/data', fetcher, { 2 revalidateOnFocus: false, 3 revalidateOnReconnect: false, 4});
1const handleChange = debounce((value) => { 2 mutate(`/api/search?query=${value}`); 3}, 300);
1const handleSave = async (newData) => { 2 await fetch('/api/save', { method: 'POST', body: JSON.stringify(newData) }); 3 mutate('/api/data', newData, false); // Update the cache without revalidation 4};
Implementing an infinite loading UI with SWR is straightforward, especially with the useSWRInfinite hook, which is designed for handling paginated data.
1import useSWRInfinite from 'swr/infinite'; 2 3const getKey = (pageIndex, previousPageData) => { 4 if (previousPageData && !previousPageData.length) return null; // Reached the end 5 return `/api/data?page=${pageIndex + 1}`; // SWR key 6}; 7 8const fetcher = (url) => fetch(url).then((res) => res.json()); 9 10function InfiniteScroll() { 11 const { data, error, size, setSize } = useSWRInfinite(getKey, fetcher); 12 13 if (error) return <div>Error loading data</div>; 14 if (!data) return <div>Loading...</div>; 15 16 return ( 17 <div> 18 {data.map((page, index) => ( 19 <div key={index}> 20 {page.map(item => ( 21 <div key={item.id}>{item.name}</div> 22 ))} 23 </div> 24 ))} 25 <button onClick={() => setSize(size + 1)}>Load more</button> 26 </div> 27 ); 28}
1const { data, error, size, setSize, isValidating } = useSWRInfinite(getKey, fetcher); 2 3if (error) return <div>Error loading data</div>; 4if (!data) return <div>Loading...</div>; 5 6return ( 7 <div> 8 {data.map((page, index) => ( 9 <div key={index}> 10 {page.map(item => ( 11 <div key={item.id}>{item.name}</div> 12 ))} 13 </div> 14 ))} 15 {isValidating && <div>Loading more...</div>} 16 <button onClick={() => setSize(size + 1)}>Load more</button> 17 </div> 18);
Ensuring that your application performs well across different devices and network conditions is essential for a good user experience. Here are some tips to enhance responsive UI performance using SWR:
1const LazyComponent = React.lazy(() => import('./LazyComponent')); 2 3function App() { 4 return ( 5 <Suspense fallback={<div>Loading...</div>}> 6 <LazyComponent /> 7 </Suspense> 8 ); 9}
1import dynamic from 'next/dynamic'; 2 3const DynamicComponent = dynamic(() => import('./DynamicComponent'), { ssr: false }); 4 5function App() { 6 return <DynamicComponent />; 7}
1import Image from 'next/image'; 2 3function UserProfile({ user }) { 4 return <Image src={user.profilePicture} alt="Profile Picture" width={500} height={500} />; 5}
1const isSlowNetwork = navigator.connection.downlink < 1.5; 2 3const { data, error } = useSWR('/api/data', fetcher, { 4 refreshInterval: isSlowNetwork ? 10000 : 3000, 5});
Effective error handling is crucial for a robust data fetching strategy. SWR provides built-in support for handling errors, making it easier to manage and display error states in your application.
1const { data, error, isLoading } = useSWR('/api/user', fetcher); 2 3if (isLoading) return <div>Loading...</div>; 4if (error) return <div>Error loading data</div>; 5return <div>Hello, {data.name}!</div>;
1const { data, error } = useSWR('/api/user', fetcher, { 2 onError: (err) => { 3 console.error('Error fetching data:', err); 4 alert('Failed to load user data'); 5 }, 6});
1const { data, error } = useSWR('/api/user', fetcher, { 2 onErrorRetry: (error, key, config, revalidate, { retryCount }) => { 3 if (retryCount >= 3) return; // Stop retrying after 3 attempts 4 setTimeout(() => revalidate({ retryCount }), 5000); // Retry after 5 seconds 5 }, 6});
Data prefetching is a powerful technique to improve the user experience by loading data before it is needed. This can make navigation between pages smoother and faster.
1export async function getStaticProps() { 2 const data = await fetcher('/api/data'); 3 return { props: { fallback: { '/api/data': data } } }; 4} 5 6function Page({ fallback }) { 7 return ( 8 <SWRConfig value={{ fallback }}> 9 <Component /> 10 </SWRConfig> 11 ); 12}
1import { mutate } from 'swr'; 2 3const prefetchData = async () => { 4 const data = await fetcher('/api/data'); 5 mutate('/api/data', data, false); // Prefetch and update cache without revalidation 6}; 7 8useEffect(() => { 9 prefetchData(); 10}, []);
Custom error handling callbacks provide fine-grained control over how errors are managed in your application. These callbacks can be used for logging, user notifications, and implementing custom retry strategies.
1const { data, error } = useSWR('/api/user', fetcher, { 2 onError: (error) => { 3 console.error('An error occurred:', error); 4 alert('Failed to load data. Please try again.'); 5 }, 6});
1const { data, error } = useSWR('/api/user', fetcher, { 2 onErrorRetry: (error, key, config, revalidate, { retryCount }) => { 3 if (retryCount >= 5) return; // Stop retrying after 5 attempts 4 const timeout = Math.min(1000 * 2 ** retryCount, 30000); // Exponential backoff 5 setTimeout(() => revalidate({ retryCount }), timeout); 6 }, 7});
In conclusion, leveraging Next.js SWR in your applications can significantly enhance data fetching, performance, and user experience. By understanding the fundamentals of SWR, including the useSWR hook, fetcher functions, and configuration options, you can implement efficient data management strategies.
Advanced data fetching patterns, such as conditional fetching, pagination, and real-time updates, along with robust error handling and data prefetching techniques, ensure that your applications remain responsive and reliable. Embracing these practices will help you build scalable and maintainable applications with a seamless user experience.
Tired of manually designing screens, coding on weekends, and technical debt? Let DhiWise handle it for you!
You can build an e-commerce store, healthcare app, portfolio, blogging website, social media or admin panel right away. Use our library of 40+ pre-built free templates to create your first application using DhiWise.