Updating the Cache
Time is our most valuable resource, and to save the user from seeing a loading spinner every time they navigate to a different page, most web apps employ a local cache. A local cache saves the result of API requests and uses that saved result the next time your app sends the same request, enabling the interface to be instantly rendered the second time around.
Libraries like React Query TanStack Query, SWR, and Apollo all offer a local cache, which works great out of the box for basic caching scenarios. But complexity arises when you make changes to your data through your API. Your cache is usually tied to a specific request and doesn’t know that changing an item should also change the result of your query request.
Going back to our task list example, if you have a query that loads your tasks, and then you send a request to mark a task as complete, your local cache might not be able to infer the new result of your query, especially if there are any kinds of filter or sorting logic being applied in the server.
The straightforward approach is to refresh the query to get an up-to-date result from the server itself. But whether you manually or automatically trigger the list refresh, keep in mind that your user will now have to wait for two requests to complete before the app is updated: one to update the data and another to refresh the list.
That’s where manually updating the cache comes in. If you want your list to reflect the changes of your user instantly, you need to access and update the cache after every action. Here’s how it could look in code:
function App() {
const { data, mutate } = useSWR("task-list", fetchTasks)
function handleTaskChange(task) {
saveTask(task)
// Manually replace the old task with the new task
mutate(data.map(item => item.id === task.id ? task : item))
}
return <TaskList tasks={tasks} onTaskChange={handleTaskChange}>
}
The mutate
call ensures that the task list cache is updated immediately, and the app will feel much faster!
But this approach has plenty of drawbacks from the developer experience:
As we discussed in the “Manually Refreshing” post, this relies on the developers to keep all the causality relationships in their heads.
It also duplicates the server logic in the client, but without any guard rails to ensure that it would require future changes to update both client and server.
And lastly, because these updates can be very complex, it likely makes the code harder to read and understand.
In summary, if you want to provide the best dynamic experience to your users, you will need to keep a cache with a local copy of your data and update it after every action. Unfortunately, you need to manually change the cache for most frameworks and libraries today.
If you use a framework that takes care of this for you, or you’re personally working on a solution to this problem, please reach out! I love learning more about this topic!
In future posts, we’ll explore alternatives to maintaining a quick response time while reducing these drawbacks, like direct database access (like Firebase and Supabase) and database replication.
Don’t forget to subscribe!
Status
Nov 28 to Dec 4
📰 Selfeed
Self-Improvement Feed
I continued experimenting with tracking habits, now building something more generic to fit the needs of the friends I talked to about it. Habits can be tracked as numbers, (like number of book pages read, or minutes working out) or as simple checkboxes if you did or didn’t do that habit. It’s an early prototype just focused in being used in user interviews, which I’ll talk more about them next week!
📺 Screen Box
Persistent Multiple Desktops
From a feature request from a user that needed to copy their layouts to a new computer, I worked on allowing layouts to be exported and imported.
Projects by the Numbers
▶️ VSCode Run Function
Downloads: 73
(previous: 86)
📺 Screen Box
WAU: 10
(previous: 11)
📰 Selfeed
WAU: 2
(previous: 2)