An introduction to useSWR for remote data fetching
Here is simple step by step tutorial that gets you started with the useSWR hook. An alternative to useSWR would be using the useEffect hook to fetch data for your react applications. So what does useSWR bring to the table that makes it a very popular remote data fetching tool?
SWR gets its name from
stale-while-revalidate
, a caching strategy that is gaining popularity in the frontend realm. It allows us to load cached content right away, while at the same time refreshing that content so that the updated content is served in the future. For us, it’s the perfect trade-off between performance and user experience. useSWR — My New Favorite React Library
With useSWR, we have caching implemented under the hood, great user experience improvement and it’s light weight!
We are going to build an application (get a quick view in this sandbox) that renders posts that we are going to fetch from the https://jsonplaceholder.typicode.com/posts API. This example will be essential for us to see the benefits of using the useSWR hook over the normal useEffect hook when working with APIs. Since we are building a page which will list posts, we get the benefit of caching posts which we will visit. Which means that, if we go ahead and visit those posts again subsequent times, they render out without loading. What if a post changes? Maybe we are the author of those posts and we have updated information on the post. What will useSWR do in this case? useSWR will compare the cache with the updated information from each post to make sure to render the data that is not stale to our posts viewers. Cool, right?
To get started with this tutorial boot up your project using create-react-app.
$cd desktop$npx create-react-app swr-medium-example$cd swr-medium-example$code .
Ooh, the last command code . let’s us open up our application on VScode.
Navigate to the App.js file on our application and clean it up so that we have this instead,
import './App.css';
const App = () => {
return (
<div className="App"> </div> );}export default App;
Delete what’s in the App.css file and have this instead
.App {}
We have cleaned up everything that was there in the App.js and App.css files so that we can start writing our changes from scratch.
Before we start writing any code, let’s start by installing the swr package from npm (which is roughly 230 kB, quite small right 😃) by running the following command
npm i swr
Amazing! Let’s start by creating our own hook which will ultimately contain data from our API. We will call this hook useAPI.js and we will store it in src/useApi.js
Inside useAPI.js, we will start by importing the useSWR hook
import useSWR from 'swr'
In the same useAPI.js file, let’s continue by defining the API endpoint that we are using for this application and set it in a variable called API
const API = `https://jsonplaceholder.typicode.com/posts`
The useSWR hook takes in the API endpoint as the first parameter and the fetcher function as the second parameter. And from it we take out data and error. We will define the fetcher function in the same useAPI.js. Such that we have this
const fetcher = (url) => fetch(url).then((res) => res.json())
We use the normal fetch API for making requests and fetching resources.
Now we write the useAPI hook as the following
const useAPI = (id = '') => { const { data, error } = useSWR(API + `/${id}`, fetcher) return { data: data, error: error, }}export default useAPI
This useAPI hook will take in an id which can either be a string or a number. By default we pass in an empty string to the useAPI hook.
Our src/useAPI.js file now looks like this
import useSWR from 'swr'const API = `https://jsonplaceholder.typicode.com/posts`
const fetcher = (url) => fetch(url).then((res) => res.json())const useAPI = (id = '') => { const { data, error } = useSWR(API + `/${id}`, fetcher) return { data: data, error: error, }}export default useAPI
In the App.js file we start by introducing state and call it activePost. activePost state which is initialized to null, will help us to render our two components (SinglePost and AllPosts which we will write later) based on whether activePost is falsy or truthy.
Let’s continue by writing the AllPosts component
const AllPosts = ({ setActivePost }) => { const { data, error } = useAPI() return ( <> <h2>All Posts</h2> {!data && !error && <h1>loading...</h1>} {error && <h1>Error!</h1>} {data && data.map((item) => ( <div key={item.id}> <div style={{ marginTop: '1rem' }}> {item.title} <button onClick={() => setActivePost(item.id)}>
View Post
</button>
</div> </div> ))} </> )}
Remember the useAPI hook returns data and error (if there is any error), that is why we destructure data and errors. We then go ahead and map over the data state and show the title and a button which sets the activePost to the id of the post clicked.
We should also have a SinglePost component which will show the details of a single post which was clicked. We just pass in the id of the post clicked into the useAPI hook. The useAPI hook in-turn appends the id to the end of the url we use to fetch a single post.
const SinglePost = ({ activePost, setActivePost }) => { const { data, error } = useAPI(activePost) return ( <div> <h3>Single Post</h3> {!data && !error && <h1>Loading single Post...</h1>} {error && <h1>Error loading single post!</h1>} <div> <h3>{data?.title}</h3> <div>{data?.body}</div> </div> <button onClick={() => setActivePost(null)}>Back</button> </div> )}
The SinglePost component takes in activePost and setActivePost props which actually do what the wordings mean. We use activePost to get data from the useAPI hook on the other hand, we use setActivePost to set back the activePost state back to null, which means that when we click the button we’ve written above in the SinglePost components we set the activePost to null which is falsy and we fallback to viewing the AllPosts component.
We can now write our default App function
export default function App() {const [activePost, setActivePost] = useState(null) return ( <div className="App"> <h1>useSWR example</h1> {activePost ? (<SinglePost activePost={activePost} setActivePost={setActivePost}/> ) : ( <AllPosts setActivePost={setActivePost} /> )} </div> )}
The ternary operator above renders the SinglePost component if the activePost state is truthy and renders the AllPosts component if the activePost state is falsy.
This is what we now have in the App.js file
import * as React from 'react'import './App.css'import useAPI from './useAPI'const { useState } = Reactconst AllPosts = ({ setActivePost }) => { const { data, error } = useAPI() return ( <> <h2>All Posts</h2> {!data && !error && <h1>loading...</h1>} {error && <h1>Error!</h1>} {data && data.map((item) => ( <div key={item.id}> <div style={{ marginTop: '1rem' }}> {item.title} <button onClick={() => setActivePost(item.id)}>
View Post
</button> </div> </div> ))} </> )}const SinglePost = ({ activePost, setActivePost }) => { const { data, error } = useAPI(activePost) return ( <div> <h3>Single Post</h3> {!data && !error && <h1>Loading single Post...</h1>} {error && <h1>Error loading single post!</h1>} <div> <h3>{data?.title}</h3> <div>{data?.body}</div> </div> <button onClick={() => setActivePost(null)}>Back</button> </div> )}export default function App() { const [activePost, setActivePost] = useState(null) return ( <div className="App"> <h1>useSWR example</h1> {activePost ? ( <SinglePost activePost={activePost} setActivePost {setActivePost} /> ) : ( <AllPosts setActivePost={setActivePost} /> )} </div> )}
Let’s now boot up our application and see what we have by starting the server using
npm run start
You should see the screen below if you were able to follow along,
Let’s do some styling to have our posts centered together with some styling for the div containing the post and the button
In our App.css file, we include the following
.App { text-align: center; font-size: 0.95em;}.post__container { display: grid;}
On interacting with our application, we figure out that after we visit a post from our posts, subsequent visits are cached and the they appear really fast into out page like so,
That’s what I had for today regarding the useSWR hook, feel free to submit a pull request with your proposed changes here.
Happy hacking 🎉