Fetching Data In React Apps And Optimistic UI Updates Using SWR 2.0
Discover how SWR 2.0 simplifies data fetching and enables Optimistic UI Updates and more such features in React applications.
Table of contents
So, In my last blog, I talked about how we can fetch data and perform CRUD operations on API using Axios. There was an imminent problem with Axios. It needed a page refresh on any CRUD operation to show the mutated data.
While this is OK with Vanilla JavaScript Websites, but can widely hamper the experience of Single Page Applications(SPA). We can in turn use the useEffect hook in react to show changes by re-rendering the component on every CRUD operation. But this in turn can be inefficient and can have a bad Developer Experience(DX) as we would have to write the same useEffect code again and again for every change.
SWR (Stale-While-Revalidate)
As stated in the official Docs, SWR is a strategy to first return the data from the cache (stale), then send the fetch request (revalidate), and finally, come up with the up-to-date data. The new SWR 2.0 update comes packed with features such as:-
New Mutation API
Optimistic UI Updates
Preloading Data and many more...
In this blog article, We are primarily going to deal with the Mutation API and Optimistic UI Updates offered by SWR 2.0.
useSWRMutation Hook
In SWR 2.0, the new hook useSWRMutation
makes it even simpler to remotely change data using a declarative API. You can set up a mutation using the hook, and then activate it later:
import useSWRMutation from 'swr/mutation'
async function sendRequest(url, { arg }) {
return fetch(url, {
method: 'POST',
body: JSON.stringify(arg)
})
}
function App() {
const { trigger, isMutating } = useSWRMutation('/api/user', sendRequest)
return (
<button
disabled={isMutating}
onClick={() => trigger({ username: 'johndoe' })}
>{
isMutating ? 'Creating...' : 'Create User'
}</button>
)
}
The example above defines a sendRequest
mutation that affects the '/api/user'
resource. Unlike useSWR
, useSWRMutation
will not immediately start the request upon rendering. Instead, it returns a trigger
function that can later be called to manually start the mutation.
The sendRequest
function will be called when the button is clicked, with the extra argument { username: 'johndoe' }
. The value of isMutating
will be set to true
until the mutation has finished.
Optimistic UI Updates
Optimistic UI is an excellent model for creating websites that feel fast and responsive. However, it can be difficult to implement correctly. SWR 2.0 has added some new powerful options to make it easier.
Let’s say we have an API that adds a new todo to the todo list and sends it to the server:
await addNewTodo('New Item')
In the UI, we use a useSWR
hook to display the todo list, with an “Add New Item” button that triggers this request and asks SWR to re-fetch the data via mutate()
:
const { mutate, data } = useSWR('/api/todos')
return <>
<ul>{/* Display data */}</ul>
<button onClick={async () => {
await addNewTodo('New Item')
mutate()
}}>
Add New Item
</button>
</>
However, the await addNewTodo(...)
request could be very slow. When it’s ongoing, users still see the old list even if we can already know what the new list will look like. With the new optimisticData
option, we can show the new list optimistically, before the server responds:
const { mutate, data } = useSWR('/api/todos')
return <>
<ul>{/* Display data */}</ul>
<button onClick={() => {
mutate(addNewTodo('New Item'), {
optimisticData: [...data, 'New Item'],
})
}}>
Add New Item
</button>
</>
SWR will immediately update the data
with the optimisticData
value, and then send the request to the server. Once the request finishes, SWR will revalidate the resource to ensure it’s the latest.
Like many APIs, if the addNewTodo(...)
request returns us the latest data from the server, we can directly show that result, too (instead of starting a new revalidation)! There’s the new populateCache
option to tell SWR to update the local data with the mutate response:
const { mutate, data } = useSWR('/api/todos')
return <>
<ul>{/* Display data */}</ul>
<button onClick={() => {
mutate(addNewTodo('New Item'), {
optimisticData: [...data, 'New Item'],
populateCache: true,
})
}}>
Add New Item
</button>
</>
At the same time, we don’t need another revalidation afterwards as the response data is from the source of truth, we can disable it with the revalidate
option:
const { mutate, data } = useSWR('/api/todos')
return <>
<ul>{/* Display data */}</ul>
<button onClick={() => {
mutate(addNewTodo('New Item'), {
optimisticData: [...data, 'New Item'],
populateCache: true,
revalidate: false,
})
}}>
Add New Item
</button>
</>
Lastly, if addNewTodo(...)
fails with an exception, we can revert the optimistic data ([...data, 'New Item']
) we just set, by setting rollbackOnError
to true
(which is also the default option). When that happens, SWR will roll back data
to the previous value.
const { mutate, data } = useSWR('/api/todos')
return <>
<ul>{/* Display data */}</ul>
<button onClick={() => {
mutate(addNewTodo('New Item'), {
optimisticData: [...data, 'New Item'],
populateCache: true,
revalidate: false,
rollbackOnError: true,
})
}}>
Add New Item
</button>
</>
The final code will resemble the above example.
Note - The examples are not solely mine but are from the SWR 2.0 Official Docs. You can refer to the official docs at https://swr.vercel.app/docs/getting-started for more details.