React multiple console.log? - reactjs

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.

Related

React store API response in localStorage

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");

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);
}

Unsubscribe from timer in rxjs

Hi I have a timer running which is like it should show a component for 30sec after every 10 seconds. My code is like this`
import { timer } from "rxjs";
componentWillReceiveProps(nextProps, nextState) {
console.log("RECEIVED PROPS");
if (this.props.venuesBonusOfferDuration === 0) {
this.subscribe.unsubscribe();
}
this.firstTimerSubscription;
this.secondTimerSubscription;
// if (this.state.isMounted) {
if (
nextProps.showBonusObj &&
(!nextProps.exitVenue.exitVenueSuccess || nextProps.enterVenues)
) {
// console.log("isMounted" + this.state.isMounted);
//if (this.state.isMounted) {
let milliseconds = nextProps.venuesBonusOfferDuration * 1000;
this.source = timer(milliseconds);
this.firstTimerSubscription = this.source.subscribe(val => {
console.log("hiding bonus offer");
this.firstTimerSubscription.unsubscribe();
this.props.hideBonusOffer();
this.secondTimerSubscription = bonusApiTimer.subscribe(val => {
console.log("caling timer" + val);
this.props.getVenuesBonusOffer(
this.props.venues.venues.id,
this.props.uid,
this.props.accessToken
);
});
});
//}
} else {
try {
if (this.secondTimerSubscription != undefined) {
this.secondTimerSubscription.unsubscribe();
console.log("secondTimer UNSUBSCRIBED");
}
if (this.firstTimerSubscription != undefined) {
this.firstTimerSubscription.unsubscribe();
console.log("firstTimer UNSUBSCRIBED");
}
} catch (error) {
console.log(
"error when removing bonusoffer timer" + JSON.stringify(error)
);
}
//}
}
}
`
Problem is if I try to unsubscribe this * this.firstTimerSubscription* and this.secondTimerSubscription like this
try {
if (this.secondTimerSubscription != undefined) {
this.secondTimerSubscription.unsubscribe();
console.log("secondTimerunmount UNSUBSCRIBED");
}
if (this.firstTimerSubscription != undefined) {
this.firstTimerSubscription.unsubscribe();
console.log("firstTimerunmount UNSUBSCRIBED");
}
} catch (error) {
console.log("error bonusoffer timer" + JSON.stringify(error));
}
its still prints logs within timer like "hiding bonus offer" and "calling timer".
Can someone please point out the issue. It been a day since am into this.
Any help is appreciated.
The problem is that you subscribe multiple times (whenever component receives props) and reassign newest subscription to firstTimerSubscription or secondTimerSubscription references. But doing that, subscriptions does not magically vanish. To see how it works here is a demo:
const source = timer(1000, 1000);
let subscribe;
subscribe = source.subscribe(val => console.log(val));
subscribe = source.subscribe(val => console.log(val));
setTimeout(() => {
subscribe.unsubscribe();
}, 2000)
Even though you unsubscribed, the first subscription keeps emiting. And the problem is that you lost a reference to it, so you can't unsubscribe now.
Easy fix could be to check whether you already subscribed and unsubscribe if so, before subscribing:
this.firstTimerSubscription ? this.firstTimerSubscription.unsubscribe: false;
this.firstTimerSubscription = this.source.subscribe(...
I wouldn't use a second timer. Just do a interval of 10 seconds. The interval emits the iteration number 1, 2, 3..... You can use the modulo operator on that tick. Following example code (for example with 1 second interval) prints true and false in console. After true it needs 3 seconds to show false. After false it needs 1 second to show true.
interval(1000).pipe(
map(tick => tick % 4 !== 0),
distinctUntilChanged(),
).subscribe(x => console.log(x));

Grouping users into n size groups using angularfire

I am writing an Angular/Firebase application where users who visit a waiting room page are assigned a group and once that group has n users a new group is formed. Using transactions seems like the write path, but am stuck.
In the example below I have a Config service that returns an $firebaseObject
This object contains the group size or playerLimit.
angular.module('floodStudyApp')
.controller('WaitingroomCtrl', function ( $scope, $routeParams, Ref, $location, Config) {
Config.getConfig($routeParams.floodstudy).$loaded(function (config) {
$scope.floodstudyConfig = config;
var NUM_PLAYERS = config.playerLimit;
Ref.child('floodStudy/'+ config.name+ '/group' + $scope.floodstudyConfig.groups + '/players').transaction(function(playerList) {
if (playerList === null) {
playerList = {};
}
if(playerList.hasOwnProperty($routeParams.player)){
console.log("you already are here dude!");
return;
}
if(Object.keys(playerList).length % NUM_PLAYERS === 0) {
$scope.floodstudyConfig.groups++;
$scope.floodstudyConfig.$save();
}
playerList[$routeParams.player] = {
name: $routeParams.player,
startTime: Firebase.ServerValue.TIMESTAMP,
playerIndex: Object.keys(playerList).length+1
};
return playerList;
//}
}, function(error, committed, snapshot){
if(!error, committed){
$scope.$apply(function () {
$location.path('floodstudy/' + $routeParams.floodstudy+ '/group' + $scope.floodstudyConfig.groups + '/' + $routeParams.player);
});
}
});//end transaction
});// end get config
});
Assuming a surge of users, I need each group to have exactly n users. The code above handles a trickle of users, but not a surge. When hammered upon the groups contain 2-6 users each. Any suggestions on how to accomplish this would be greatly appreciated.
Here is a sample output after a surge: https://gist.github.com/shawnzam/041f4e26bc98a3f89a7b
Rather than attempting to do this with arrays, given all the reasons sequential, numeric ids fall over in distributed data, I'd instead recommend that you use a counter, simplify, and have great justice from every Zig.
Suggested data structure:
/rooms/$roomid/counter
/members/$counter_value/$memberid
Function to update the counter:
angular.factory('updateRoomCounter', function($q, Ref) {
return function(roomId) {
return $q(function(resolve, reject) {
Ref.child('rooms/'+roomId+'/counter').transaction(function(currentValue) {
if( currentValue >= <NUM_PLAYERS> ) { return; }
return (currentValue||0)+1;
}, function(err, committed, snap) {
if( err || !committed ) {
reject(err || 'Too Many Players');
}
else {
resolve(snap.val());
}
});
});
}
});
Using the counter update:
angular.controller(..., function(updateRoomCounter, Ref) {
function addAPlayer(roomId, userId) {
updateRoomCounter(roomId).then(function(myIndex) {
Ref.child('members/'+roomId+'/'+myIndex).set(userId, <doneFunction>, <errorFunction>);
}, function() {
// failed: try the next room?
})
}
});
Security rules to enforce structure:
{
"rules": {
"rooms": {
"$room_id": {
"counter": {
".write": "newData.exists()",
".validate": "newData.isNumber() && (newData.val() == data.val() + 1 || !data.exists() && newData.val() == 1)"
}
}
},
"members": {
"$room_id": {
"$counter_value": {
".write": "newData.val() === auth.uid && !data.exists() && newData.exists() && $counter_value <= root.child('rooms/'+$room_id+'/counter').val()"
}
}
}
}
}
Kato's answer is a good approach to implement your use-case. I want to chime in on why you are having this problem to begin with.
Firebase transactions work on a mixed client-and-server model. The code that you write for a transaction() runs on the client. It gets the current value as input and returns the new value (or nothing if no change is needed). This entire "current value + new value" package is then sent to the Firebase servers. The Firebase server then does a compare-and-set. If the stored value is the same as what you started the transaction with, your new value will be used. If the current value has changed in the meantime, your new value is rejected and your transaction handler is run again.
In your transaction handler, you don't just update the current value. You also have this snippet:
if(Object.keys(playerList).length % NUM_PLAYERS === NUM_PLAYERS) {
$scope.floodstudyConfig.groups++;
$scope.floodstudyConfig.$save();
}
Since this modifies data outside of the current/return value, it is not part of the transaction. So even if the new value that you return from the transaction on the server is rejected, you will already have updated the group.
I believe I have a solution. Instead of counting just the players per group I also count the current group. I do this in a single object of form {currentPlayers: 0, groups: 0}.
Function to update the counter object:
angular.module('floodStudyApp')
.factory('updateGroupCounter', function($q, Ref) {
return function(floodstudy, NUM_PLAYERS) {
return $q(function(resolve, reject) {
Ref.child('floodStudyConfig/'+floodstudy+'/currentPlayers').transaction(function(currentValue) {
if(currentValue.currentPlayers >= NUM_PLAYERS ) { return {currentPlayers: 1, groups: currentValue.groups +1}; }
return ({currentPlayers: currentValue.currentPlayers+1, groups: currentValue.groups});
}, function(err, committed, snap) {
if( err || !committed ) {
reject(err || 'Too Many Players');
}
else {
resolve(snap.val());
}
});
});
}
});
Function to update the counter object:
angular.module('floodStudyApp')
.controller('WaitingroomCtrl', function ( $scope, $routeParams, Ref, $location, Config, updateGroupCounter) {
function addAPlayer(roomId, userId, NUM_Player) {
updateGroupCounter(roomId, NUM_Player).then(function(currentConfig) {
Ref.child('floodstudy/' + roomId + '/group' + currentConfig.groups+ '/players/' + currentConfig.currentPlayers).set({
user: userId,
playerIndex: currentConfig.currentPlayers,
})
}, function() {
console.log("full");
})
}
Config.getConfig($routeParams.floodstudy).$loaded(function (config) {
$scope.floodstudyConfig = config;
var NUM_PLAYERS = config.playerLimit;
addAPlayer($routeParams.floodstudy, $routeParams.player, NUM_PLAYERS);
});// end get config
});

Google calendar API do not return events list

Recently after adding new events in Google calendar and try to fetch it from Google calendar API, I never get the new events list for the primary calendarId of my account.
Also I tried to fetch from developer console:
https://developers.google.com/google-apps/calendar/v3/reference/events/list#try-it
which gives the same empty list of items as response, even though the future events exists.
But I am able to fetch the events list of other calendar list. eg. Holidays, shared calendar from friends etc.
I also had the problem of events().list(calendarId=<calendar_id>, singleEvents=False) not returning any events (using python API). I tested with a secondary calendar with one recurring event. Solved by adding parameter maxResults=9999. It turned out that maxResults value 1413 or higher is needed to return the event. Weird, seems to be a bug.
Try adding singleEvents:true to your request.
I was having the same issue. In my case, using Javascript, this works (returns several single, non-recurring events) :
var calendar = googleapis.calendar('v3');
calendar.events.list({
auth: auth,
calendarId: 'primary',
timeMin: (new Date()).toISOString(),
maxResults: 10,
singleEvents: true,
orderBy: 'startTime'
}, function(err, response) {
if (err) {
console.log('The API returned an error: ' + err);
return callback(err);
}
console.log(response);
var events = response.items;
if (events.length == 0) {
console.log('No upcoming events found.');
} else {
console.log('Upcoming 10 events:');
for (var i = 0; i < events.length; i++) {
var event = events[i];
var start = event.start.dateTime || event.start.date;
console.log('%s - %s', start, event.summary);
}
return callback(null, events);
}
});
But this does not (doesn't return any events including those that are on the 'primary' calendar and are single, non-recurring events). The only difference from above code is singleEvents parameter is commented out so it's value will default to false.
var calendar = googleapis.calendar('v3');
calendar.events.list({
auth: auth,
calendarId: 'primary',
timeMin: (new Date()).toISOString(),
maxResults: 10,
//singleEvents: true,
orderBy: 'startTime'
}, function(err, response) {
if (err) {
console.log('The API returned an error: ' + err);
return callback(err);
}
console.log(response);
var events = response.items;
if (events.length == 0) {
console.log('No upcoming events found.');
} else {
console.log('Upcoming 10 events:');
for (var i = 0; i < events.length; i++) {
var event = events[i];
var start = event.start.dateTime || event.start.date;
console.log('%s - %s', start, event.summary);
}
return callback(null, events);
}
});

Resources