Reputation: 195
I have a array of objects in API call which I'm calling from useEffect
. Basically what I want is that, I want to hit the API only once and set the array in localStorage
.
Now, if localStorage has some data related to it the set array using useState
will be set in it through localStorage
otherwise if it doesn't contain any item related to it, then it would hit API again to fetch the array and set to localStorage
.
The code what I have done so far is:
const [loadedg, setloadedg] = useState(false);
const [domainListGroupItems, setdomainListGroupItems] = useState([]);
useEffect(() => {
//---------->Groups Lists<----------------
if(!loadedg) {
setdomainListGroupItems(JSON.parse(localStorage.getItem('leftgroup')))
}else{
ApiService(leftgroups).then((response) => {
if (response.statusCode === 1) {
const item = response.domainGroupList
localStorage.setItem('leftgroup', JSON.stringify(item));
setloadedg(true);
console.log(item);
} else {
setloadedg(false);
}
})
.catch((error) => {
console.log(error)
})
}
}, [loadedg]);
The concept which I was using here is basically using boolean condition which I think I should be using. But if it can be done without using the boolean value, that would be great for me.
How will I resolve this?. Thank you.
Upvotes: 1
Views: 1358
Reputation: 10976
So you want to use localStorage as client cache, it's more interesting to set a date for expiry than to check for existence of the key. In the code snippet below I shimmed localStorage
as ls
and the API call as a promise
with setTimeout
for testing purposes.
I extracted the apiCall effect as standalone, so you can use it anywhere, and gave it a skip
parameter. In the snippet, the call will be skipped if the localstorage cache dates from less than 7 days ago. You can edit the snippet and uncomment the 2 lines to test what happens when the cache is recent. Be sure to expand the snippet to "Full page" else the console logs hide the localStorage value :)
const useState = React.useState,
useEffect = React.useEffect;
// network call shim
function ApiService() {
return new Promise((resolve, reject) => {
setTimeout(() => {
try {
resolve({
statusCode: 1,
domainGroupList: ['Hello','World']
});
} catch (err) {
reject(new Error('500 Server Error'));
}
}, 1000);
});
}
// localstorage shim
const ls = Object.create({
getItem(key) {
return this[key];
},
setItem(key, value) {
this[key] = value;
}
});
// comment out to see what happens when ls has been populated less than 7d ago
// ls.setItem('lastfetch', new Date().toISOString());
// ls.setItem('groupleft', []);
function isLessThan7DaysAgo(date) {
const today = new Date();
return date > new Date(today.setDate(today.getDate()-7));
}
const apiCallEffect = (onLoad, skip) => () => {
if (skip) return;
ApiService().then((response) => {
if (response.statusCode === 1) {
const items = response.domainGroupList;
ls.setItem('leftgroup', JSON.stringify(items));
ls.setItem('lastfetch', new Date().toISOString());
if (onLoad)
onLoad(items);
}
})
.catch((error) => {
console.log(error.message)
});
}
const App = ({
initialItems,
onLoad
}) => {
const skip = ls.getItem('lastfetch') && isLessThan7DaysAgo(new Date(ls.getItem('lastfetch')));
const [loaded, setloaded] = useState(skip);
const [groupItems, setGroupItems] = useState(initialItems);
console.log(skip ? 'skipping fetch, getting from ls' : 'doing fetch, storing in ls');
useEffect(apiCallEffect((items) => {
setloaded(true);
setGroupItems(items);
}, skip), [groupItems]);
return <div>
{ loaded ? <ul>{groupItems.map(x => (<li>{x}</li>)) }</ul> : <div>Loading...</div> }
<strong>In localstorage</strong><br/>
<pre>{JSON.stringify(ls, null, 2)}</pre>
</div>;
};
const storedDomainListGroupItems = ls.getItem('leftgroup') || [];
ReactDOM.render(<App initialItems={storedDomainListGroupItems}/>, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
<strong>In app:</strong>
<div id="app"></div>
Upvotes: 1
Reputation: 97
To limit useEffect to fire only once, you can remove the loadedg from depedency array, now it will simply check if data is already there in local storage or not, if not, it will fetch the data and it will set the data in local storage.
Link for article regarding useEffect and dependency array
Hope it solves your problem.
Upvotes: -1
Reputation: 1651
You can initialize your state with a value
const [domainListGroupItems, setdomainListGroupItems] = useState(JSON.parse(localStorage.getItem('leftgroup')));
and then, in your useEffect
, just checking if domainListGroupItems
is null or not
useEffect(() => {
if (domainListGroupItems) return; // already set from localStorage
ApiService(leftgroups).then((response) => { .... })
}, [domainListGroupItems]);
Upvotes: 0