React store API response in localStorage - reactjs

I have an API that returns a list which is shown as a grid in my main component. I store the list in localStorage bcz I don't want that API to be called every time the component is rendered. It looks something like this:
const setOldData = (dataRN) => {
console.log("Using old data...")
setContent(JSON.parse(dataRN));
let tmpArr = JSON.parse(dataRN).CodeList;
setData(JSON.parse(dataRN).RNList);
var tmp = groupBy(tmpArr, 'groupname');
setAktNaloga(tmp.GetListaAktivnostNaloga);
setTipAkcije(tmp.GetListaVrstaAkcijeNaloga);
setKorGrupa(tmp.GetListaKorisnickihGrupa);
setLinijaProdukta(tmp.GetListaLinijeProdukataNaloga);
setPrioritet(tmp.GetListaPrioritetNaloga);
setStanje(tmp.GetListaStanjeNaloga);
setStatus(tmp.GetListaStatusaNaloga);
setLokacija(tmp.GetListaPodrucjeOdrzavanja);
}
useEffect(() => {
showLoader();
console.log("Getting srv data");
window.addEventListener('resize', handleResize);
let unmounted = false;
let source = axios.CancelToken.source();
let datestring = dateStart.getDate() + "." + (dateStart.getMonth() + 1) + "." + dateStart.getFullYear();
console.log(datestring);
setDateStartSend(datestring);
let datestringEnd = dateEnd.getDate() + "." + (dateEnd.getMonth() + 1) + "." + dateEnd.getFullYear();
console.log(datestringEnd);
setDateEndSend(datestringEnd);
if (window.localStorage !== undefined) {
const dataRN = window.localStorage.getItem('rnList');
dataRN !== null ? setOldData(dataRN) : RNService.getRNList(source.token, datestring, datestringEnd, currentUser.userName).then(
(response) => {
if (!unmounted) {
localStorage.setItem('rnList', JSON.stringify(response.data));
setContent(response.data);
console.log('getRNLista', response.data);
let tmpArr = response.data.CodeList;
setData(response.data.RNList);
var tmp = groupBy(tmpArr, 'groupname');
console.log("Statusi; ");
console.log(tmp.GetListaStatusaNaloga);
setTipZahtjeva(tmp.GetListaVrstaZahtjeva);
console.log(tmp.GetListaVrstaZahtjeva);
console.log("AAA");
setAktNaloga(tmp.GetListaAktivnostNaloga);
setTipAkcije(tmp.GetListaVrstaAkcijeNaloga);
setKorGrupa(tmp.GetListaKorisnickihGrupa);
setLinijaProdukta(tmp.GetListaLinijeProdukataNaloga);
setPrioritet(tmp.GetListaPrioritetNaloga);
setStanje(tmp.GetListaStanjeNaloga);
setStatus(tmp.GetListaStatusaNaloga);
setLokacija(tmp.GetListaPodrucjeOdrzavanja);
console.log("Count: " + response.data.RNList.length);
hideLoader();
}
},
(error) => {
if (!unmounted) {
console.log("Error rq");
console.log(error.response.status);
if (error.response.status == 401) {
alert(error.response.data.message)
window.localStorage.removeItem('rnList')
logOut();
} else {
//alert(error.response.data.message)
alert("Problem");
}
}
}
);
}
hideLoader();
}, [reload]);
So, in useEffect it is checked if there is something in localStorage, if there is --> don't call the API; if there isn't --> call the API. Also, my login token lasts for an hour, so if you logged in now, in an hour, if you are in this component, useEffect would throw an error and kick you out for you to login again.
BUT, here comes the problem --> the error can't be thrown bcz once I filled up localStorage, I don't know how to empty it properly, or, what I want to say, I don't know WHERE exactly should I empty it.
I want to empty it once the token has ran out, but I don't know the best possible solution for it. Should I make an API that just checks the token validity and put it in setOldData method and, if it has ran out, throw an error, kick the user out and clear the localStorage?
Or, should I use sessionStorage, Redux...?

const currentTime = Date.now() / 1000; // to get in milliseconds
if (decoded.exp < currentTime) {
// Logout user
store.dispatch(logoutUser());
// Redirect to login
window.location.href = "./sign-in";
}
You may use Redux to manage the state of the application and like the above example you may check the expiration time and log out the user and clear the loaclstoreage
//Authaction.js -- redux
localStorage.removeItem("your-item");

Related

React multiple console.log?

Im creating a function that sends an email based on the expiry date, however, upon trial, the function works fine but the email gets sent multiple times and the console.log is shown multiple times. Does anyone know how to counteract?
if (product?.expiry) {
var arr = []
var today = new Date()
var expiry = new Date(product?.expiry)
var week = new Date(expiry.setDate(expiry.getDate() - 7))
var template = {
message: product.name + " is reaching expiry date",
footer: "Expiry Date: " + week.toDateString()
}
const interval = setInterval(() => {
if (arr.length > 0) {
clearInterval(interval)
console.log(arr)
}
if (today !== expiry) {
// actual emailing
emailjs.send(SERVICE_API, TEMPLATE_API, template, PUBLIC_API)
.then(function(response) {
console.log('SUCCESS!', response.status, response.text);
}, function(error) {
console.log('FAILED...', error)
});
arr.push('found')
}
}, 1000)
}
ALERT: I am aware that the function is sending the email when it is not the expiry date, i just want to fix up this console.log error
**rest of function: **
const expiryEmail = () => {
if (today !== week) {
emailjs.send(SERVICE_API, TEMPLATE_API, template, PUBLIC_API)
.then(function(response) {
console.log('SUCCESS!', response.status, response.text);
}, function(error) {
console.log('FAILED...', error)
});
}
}
setTimeout(() => {
})
useEffect(() => {
if (!product) {
return;
}
//problem is that the condition returns null first time around
expiryEmail()
},[])
Try to use useEffect hook instead to set interval I am sharing the code try to use that
var today = new Date()
var expiry = new Date(product?.expiry)
var week = new Date(expiry.setDate(expiry.getDate() - 7))
var template = {
message: product.name + " is reaching expiry date",
footer: "Expiry Date: " + week.toDateString()
}
useEffect(()=>{
if (today !== expiry) {
emailjs.send(SERVICE_API, TEMPLATE_API, template, PUBLIC_API)
.then(function(response) {
console.log('SUCCESS!', response.status, response.text);
}, function(error) {
console.log('FAILED...', error)
});
arr.push('found')
}
},[])
The first condition inside your setInterval method's callback, ie.
if (arr.length > 0) {
clearInterval(interval)
console.log(arr)
}
does not have a return statement, which means it will continue on to your next if block, whose condition is satisfied because you are checking is today's date is not the same as expiry 's date, which as you alerted is a conscious decision, and therefore print the console message again.
Also, I hope by multiple you mean only twice, because that is what my answer applies for, if you meant more than that, I'd like to see the part that calls this method.
Again also, you should use Mayank's suggestion and process your code inside a useEffect block, much simpler.

Restful API failing sporadically but "catch" statement is always called, even when it does not fail

We are using a restful API to retrieve information about esports matches being played. From time to time the page simply loads, with no info being returned form the API.
I am fairly confident that the issue is with the API itself, but wanted to double-check that we are not doing anything wrong. Please see our code below:
const proxyurl = "https://cors-anywhere.herokuapp.com/";
const url = "http://datafeed.bet/en/esports.json ";
fetch(proxyurl + url)
.then(response => response.json())
.then(data => {
const list = data;
const games =
list &&
list.Sport &&
list.Sport.Events &&
list.Sport.Events.map((match) =>
match.Name.substr(0, match.Name.indexOf(","))
);
const uniqueGames = [...new Set(games)];
let combinedMatches = [];
data &&
data.Sport &&
data.Sport.Events &&
data.Sport.Events.map((game) => {
game.Matches.map((match) => {
match.Logo = game.Logo;
match.TournamentName = game.Name;
match.CategoryID = game.CategoryID;
match.ID = game.ID;
});
combinedMatches = combinedMatches.concat(game.Matches);
});
this.setState({
gameData: data,
games: games,
uniqueGames: uniqueGames,
preloading: false,
filteredGames: combinedMatches,
allMatches: combinedMatches,
count: Math.ceil(combinedMatches.length / this.state.pageSize),
});
var i;
let allMatches = 0;
let temp;
for (i = 0; i < this.state.filteredGames.length; i++) {
temp = allMatches =
allMatches + this.state.filteredGames[i].Matches.length;
}
this.setState({ allMatches: allMatches });
})
.catch(console.log('error');
Something that confuses me is that whether the data is returned or not, the "catch" statement gets called, outputting "error" to the console. I would like to build in some workaround for when the data is not returned. Would this be placed in the "catch" statement? If so, why how do I only let the catch run if the operation actually fails.
When you do this:
.catch(console.log('error'))
You immediately invoke console.log('error') and pass its result (which is undefined) to the catch. In this case it's invoking it right away, before the AJAX operation is even performed.
What you want to pass to catch is a function which would invoke that operation if/when it needs to:
.catch(e => console.log('error'))
As an aside, you'd probably also want to log the error itself so you can see what happened, as opposed to just the string 'error':
.catch(e => console.log('error', e))

How can I save a value in this React lambda function?

I have this React project template, it uses Spring for backend and a PostgreSQL database.
Here, I have the name of a caregiver, and what i want, is to get the id of that caregiver.
I don't know how to retrieve the id, from the findCaregiver method. When in the function, "result.id" will show the correct id, but when it returns to "handleSubmit", the initial method, the value is gone. I tried storing it in the current components' state, in a global variable, even in localStorage. But nothing works, after the function is done, the value is gone. Any ideas? Thank you!!
handleSubmit() {
let name = this.state.formControls.caregiverName.value;
let c = this.findCaregiver(name);
console.log("id: " + c);
findCaregiver(name) {
return API_USERS.getCaregiverByName(name, (result, status, error) => {
if (result !== null && (status === 200 || status === 201)) {
console.log("Successfully found caregiver with id: " + result.id);
//result.id shows correct id here!
} else {
this.setState(({
errorStatus: status,
error: error
}));
}
});
}
getCaregiverByName is asynchronous. When you call it, the gears are set in motion, but then the function finishes immediately. Then you return to handleSubmit, and log out nothing, because it isn't done yet.
Some time later, the get finishes, and the callback function will be called. Any code that you need to run when the result is ready needs to be done in that callback, or something called by that callback.
So if we're sticking with callbacks, you can add a callback to findCaregiver:
handleSubmit() {
let name = this.state.formControls.caregiverName.value;
this.findCaregiver(name, (id) => {
console.log("id: " + id);
});
}
findCaregiver(name, callback) {
API_USERS.getCaregiverByName(name, (result, status, error) => {
if (result !== null && (status === 200 || status === 201)) {
callback(result.id);
}
}
}
If you perfer promises to callbacks (most people do), you can instead change your code to return a promise. Perhaps getCaregiverByName could be modified to return a promise, but if you don't have access to that code you can wrap your existing code in a promise like this:
handleSubmit() {
let name = this.state.formControls.caregiverName.value;
this.findCaregiver(name)
.then((c) => {
console.log("id: " + c);
});
}
findCaregiver(name) {
return new Promise((resolve, reject) => {
API_USERS.getCaregiverByName(name, (result, status, error) => {
if (result !== null && (status === 200 || status === 201)) {
resolve(result.id);
} else {
reject(error);
}
})
});
}
And if it's returning a promise, you have the option to use async/await:
asycn handleSubmit() {
let name = this.state.formControls.caregiverName.value;
const c = await this.findCaregiver(name);
console.log("id: " + c);
}

Nested callback function (React native JS )

I have a couple of different functions that helps set different states of the component and i wish to run the functions in order of one another. I know that there are multiple posts on these but they seem to be mostly catered for running one function after the other but i have more than two functions that i need to execute in order
Desired order
1) Set state of starting and final destination
2) Run this.getDirections() (This function sets the state of arrOfPolylines which i desire to reset through resetRouteSelectionStatus())
3) Run resetRouteSelectionStatus()
4) After running these functions i wish to have an empty this.state.arrOfPolylines
Actual results
There is no error in the code but it isnt entering the resetRouteSelectionStatus() as none of the console log are printed. Can someone please guide me on the right path?
<Button
onPress={() => { //on button press set final destination and starting location
{
(this.state.tempDestination.longitude != null && this.state.tempStarting.longitude != null) &&
this.setState({
finalDestination: {
latitude: this.state.tempDestination.latitude,
longitude: this.state.tempDestination.longitude,
},
startingLocation: {
latitude: this.state.tempStarting.latitude,
longitude: this.state.tempStarting.longitude,
}
}, () => {
this.getDirections((this.state.startingLocation.latitude + "," + this.state.startingLocation.longitude), (this.state.finalDestination.latitude + "," + this.state.finalDestination.longitude),
() => {this.resetRouteSelectionStatus()});
}
);
}
}}
title="Determine Directions"
color="#00B0FF"
resetRouteSelectionStatus() {
console.log('entered reset route selection status function')
this.setState({arrOfPolyline: null }, () => {console.log("i should be null nd come first" + this.state.arrOfPolyline)}) ;
this.setState({ selectChallengeStatus: null });
this.setState({ userRouteSelectionStatus: null }); //when user click on button set seleection status to 0 so route options will be displayed again after generation new route
//this.setState({arrOfDirectionDetails: []}); // clear past direction details when user select generate route with new starting/ending location
// clear past poly lines when user has selected new routes
//console.log("everything has been reset");
}
async getDirections(startLoc, destinationLoc) {
let resp = await fetch(`https://maps.googleapis.com/maps/api/directions/json?origin=${startLoc}&destination=${destinationLoc}&key="KEY"&mode=driving&alternatives=true`)
let respJson = await resp.json();
let routeDetails = respJson.routes;
let tempPolyLineArray = [];
let tempDirArrayRoute = [];
//console.log(startLoc);
//console.log(destinationLoc);
for (i = 0; i < routeDetails.length; i++) // respJson.routes = number of alternative routes available
{
let tempDirArray = []; // at every new route, initalize a temp direction array
let points = Polyline.decode(respJson.routes[i].overview_polyline.points);
let coords = points.map((point, index) => {
return {
latitude: point[0],
longitude: point[1]
}
})
tempPolyLineArray.push(coords);
for (k = 0; k < routeDetails[i].legs[0].steps.length; k++) // for each route go to the correct route, enter legs ( always 0), get the number of instruction for this route
{
//console.log (routeDetails[i].legs[0].steps[k])
tempDirArray.push(routeDetails[i].legs[0].steps[k]) // push all instructions into temp direction array
//this.state.arrOfDirectionDetails.push(routeDetails[i].legs[0].steps[k]); // for each instruction save it to array
}
tempDirArrayRoute.push(tempDirArray); // at the end of each route, push all instructions stored in temp array as an array into state
}
this.setState({ arrOfDirectionDetails: tempDirArrayRoute });
this.setState({ arrOfPolyline: tempPolyLineArray });
//creating my html tags
let data = [];
let temptitle = "Route ";
for (let j = 0; j < routeDetails.length; j++) {
data.push(
<View key={j}>
<Button
title={temptitle + j}
onPress={() => this.updateUser(j)}
/>
</View>
)
}
this.setState({ routebox: data })
}
So a few things to note here:
I would extract the inline function for the onButtonPress function into an async function
Something along the lines of:
const onButtonPressHandler = async () => {
... put your code here
}
Then you'll modify your onButtonPress to something like:
<Button
onPress={this.onButtonPressHandler}
... rest of your props here
/>
You can then properly use async / await in the buttonPress handler.
The setState function is not a synchronous function. If you rely on the results right away you'll be disappointed.
Each time you call setState you could trigger a rerender.
I would instead merge all of your setState calls into a single at the end.
The getDirections function does not include the callback, while it should be:
async getDirections(startLoc, destinationLoc, callback) {
.....
this.setState({ routebox: data },callback());
}
or but not sure if it would be in order:
async () => {
await this.getDirections((this.state.startingLocation.latitude + "," + this.state.startingLocation.longitude), (this.state.finalDestination.latitude + "," + this.state.finalDestination.longitude) );
this.resetRouteSelectionStatus();
}
You might need edit the resetRouteSelectionStatus to be:
resetRouteSelectionStatus = async ()=>{

How do I render/animate out items that leave array

Currently, I have a scraper that scrapes slack messages and stores them in a db somewhere.
On the frontend, I am pulling every second to see if new messages pop up. And then I render those messages on screen.
If anyone on slack replies or emotes on a message, the message gets removed from the backend thus getting removed from the frontend.
What I am trying to do now is when an item gets removed, I would like to animate it somehow.
Here is some of my current code:
async componentDidMount() {
await this.grab_channels()
await this.grab_slack_user_data()
await this.grab_items()
setInterval(() => {
this.grab_items()
}, this.state.settings.seconds_per_slack_messages_pull * 1000 )
}
grab_items() {
let url = this.state.settings.api_url + 'channel/' + this.state.selected_channel + '/now'
return new Promise( resolve => {
axios.get( url )
.then( res => {
this.setState( { items: res.data } )
resolve()
} )
})
}
And finally, items get rendered:
this.props.items.map( t => {
return (
<Item
key={ t.usr + '_' + t.ts }
task={ t }
user={ this.props.slack_users[ t.usr ] }
settings={ this.props.settings }
now={ this.state.now }
/>
)
} )
I was thinking of doing some sort of check within grab_items() but I wouldn't know how to continue after that. It would be easy to determine which ones should be rendered out but the problem is actually doing it.
Anyone have experience building something like this out?
Thanks!
Using Transition Groups is one way to do this:
https://github.com/reactjs/react-transition-group
Take a look at this example:
https://reactcommunity.org/react-transition-group/transition-group
For the check part in your function grab_items
/* include "clone" so that we don't modify state directly */
import clone from 'clone'
grab_items() {
let url = this.state.settings.api_url + 'channel/' + this.state.selected_channel + '/now'
return new Promise(resolve => {
axios.get(url).then(res => {
/* figure out what items to remove before you set the state */
let itemsToShow = []
for (let i = 0; i < this.state.items.length; i++) {
let ifFound = false
let t = clone(this.state.items[i])
for (let j = 0; j < res.data.length; j++) {
if (t.key === res.data[j].key) {
ifFound = true
}
}
/* if ifFound is false, it means it is not in the messages any more. */
if(!ifFound){
t.haveAnimation = true
itemsToShow.push(t)
}
itemsToShow = itemsToShow.concat(res.data)
this.setState(itemsToShow)
}
})
})
}
Then every second when it re-pull the data, you will have a list of items to show. The list has the items need to have the "disappear" animation and also it has the new messages.
To make the animation work, in the render part:
this.props.items.map(t => {
return (
<Item
key={t.usr + '_' + t.ts}
className={t.haveAnimation ? 'animationCSS' : ''}
task={t}
user={this.props.slack_users[t.usr]}
settings={this.props.settings}
now={this.state.now}
/>
)
}
Above code should attach the css class to the Item. You can put whatever css animation in the class

Resources