Full stack applications that are decoupled (when the front end is totally detached from the backend) can have a lot of drawbacks. One of those is when communication between the two takes too long. Laggy response times from your backend greatly depreciates the user experience.
Nobody likes waiting for stuff to load. And no – loading spinners is not a solution.
Of course, you can cache the responses in the backend. But that means either you have to use some kind of storage in the server – such as a database This solution caches the responses in the client’s browser – using sessionStorage.
It is not unusual to see code like this:
useEffect(()=>{
(async()=>{
let data : FeedType = {}
const resp = await fetch(`/get-feed/${id}`)
data = await resp.json();
setData(data)
})();
},[])
Wouldn’t it be great if it doesn’t do the fetch to the backend at all?
Remember, this is only recommended if your application doesn’t require real time data. If you can afford to serve cached data (say 15 mins) – then this solution is for you.
First, let’s write the cache to sessionStorage.
useEffect(()=>{
(async()=>{
let data : FeedType = {};
const resp = await fetch(`/get-feed/${id}`);
data = await resp.json();
const currentTimeStamp = Math.floor(Date.now() / 1000);
data.cacheSet = currentTimeStamp;
sessionStorage.setItem(`id-${id}`, JSON.stringify(data));
setData(data)
})();
},[])
Right after we get the data from the backend we write to sessionStorage with a unique key. This key is necessary for later retrieval. We’re also adding a timestamp on when we created the cache – this will come in handy for later.
Let’s create a function that gets the cache:
const getCache = (id : number) : {isCacheGood : boolean, data : FeedType} => {
let isCacheGood = false;
let data : { cacheSet ?: number}= {};
const currentTimeStamp = Math.floor(Date.now() / 1000);
const threshold = 900; //15 minutes
const cacheStr = sessionStorage.getItem(`id-${id}`);
if(cacheStr){
data = JSON.parse(cacheStr);
if(data.cacheSet){
const numSeconds = Math.abs(currentTimeStamp - data.cacheSet);
if(numSeconds < threshold){
isCacheGood = true;
}
}
}
return {
isCacheGood : isCacheGood,
data : data
}
}
As you can see, just a simple function that gets the sessionStorage with our unique key. It also converts the JSON string back to an object for consumption later. Also, the logic for checking if the cache is good is in here. It return true if its less than the threshold we set (in our case 15 minutes).
Now, let’s modify our code to not fetch when cache is found and good:
useEffect(()=>{
(async()=>{
let data : FeedType = {};
const dataFromCache = getCache(id);
if(dataFromCache.isCacheGood){
data = dataFromCache.data
}else{
const resp = await fetch(`/get-feed/${id}`);
data = await resp.json();
const currentTimeStamp = Math.floor(Date.now() / 1000);
data.cacheSet = currentTimeStamp;
sessionStorage.setItem(`id-${id}`, JSON.stringify(data));
}
setData(data)
})();
},[])
So now, we first check if cache is good and use it for our data. Otherwise, we do a fetch – and write to cache.
What about invalidation?
Good question. We don’t want to keep writing to cache and not clean up right? The good thing is, sessionStorage automatically gets cleared once the user closes the browser session. Also, remember the unique key that we set – well, that keeps getting overwritten – if its the same response.
Let me know your thoughts below.
“localStorage automatically gets cleared once the user closes the browser session.”
This is not correct
You’re absolutely right. Updated the article to use sessionStorage. Thank you