Last item in array won't update with ajax request - arrays

I'm building a store dashboard in Angular 5 which shows orders that are in route and orders that have arrived. When a user clicks a button it sets the hasArrived field in the order as true. With the setInterval funciton in the store dashboard component I'm making an API call every second to get the order data and check the filed hasArrived to see if it's true or false. In the dashboard you can see orders move from inRoute to arrived in seemingly real time once this button is clicked. The only problem is when there's only a single order in the inRoute array, even when hasArrived is switched to true, it still displays on the screen in the inRoute orders. Only once you refresh the page does it update into the arrived section.
setInterval(() =>{
this.storeService.getStore(localStorage.getItem('storeId'))
.subscribe((data) => {
console.log(data);
this.store = data;
let ir = [];
let ar = [];
for(let order of data.orders){
if(order.completedPurchase === false){
if(order.hasArrived === false){
ir.push(order);
this.inRoute = ir;
}
if(order.hasArrived == true){
ar.push(order);
this.here = ar;
}
}
}
}),
error => console.log(error);
}, 1000);

try the following code:
setInterval(() => {
this.storeService.getStore(localStorage.getItem('storeId'))
.subscribe((data) => {
console.log(data);
this.store = data; // do you need this?
this.inRoute = [];
this.here = [];
(data.orders || []).forEach((order) => {
if (!order.completedPurchase) {
if (order.hasArrived) {
this.here.push(order);
} else {
this.inRoute.push(order);
}
}
});
}),
error => console.log(error);
}, 1000);
In short, I guess the root cause is that you do not reassign this.inRoute when its single item changes hasArrived from false to true.
I have tried to keep as much untouched as I could, but there some areas to think about:
try use setTimeout: fetch orders every second after the previous data arrived
do not forget to clean-up: call either clearInterval/clearTimeout when the component is being destroyed
I guess there could be a solution with rx-js, but I am not familiar with it..

Related

Making an API request every X amount of seconds in reactJS

On entering CurrencyPage, I am trying to get the list of currencyConversionRates from database and then subsequently set an interval to constantly get the new updated list of currencyConversionRates every 6 seconds.
Up till this point, everything works fine. However, I am unable to pass the value of the setInterval to the cleanup function. The value being returned is always in the format of a promise
PromiseĀ {<fulfilled>: 1}
How can I pass the setInterval value to the cleanup function so that the interval stops being executed upon navigating to other pages
Use Effect
useEffect(() => {
const getCurrencyConversionRates=async()=>{
console.log("Retrieving list of currency conversion rate");
await retrieveCurrencyConversionRateFunction();
//Gets new information every 6 seconds
const intervalCall = setInterval(async() => {
console.log("Getting new currency conversion rates!");
const currTime = new Date().toLocaleTimeString();
console.log("Time -> "+currTime);
await retrieveCurrencyConversionRateFunction();
}, 60*100);
//Have to wait until currencyConversionRateFunction is loaded before the map function is called on it in the render view otherwise will have error
setLoading(false);
return intervalCall;
}
let intervalCall = getCurrencyConversionRates();
//Clear interval
return () => {
console.log("CLEARING CURRENCY INTERVAL HERE");
console.log(intervalCall);
clearInterval(intervalCall);
};
},[]);
Solution Based on Azzy's answer
Rather than to try and return the interval in getCurrencyConversionRates(), a simple alternative is to just initialize a variable to store the setInterval as it is created within the function.
Note to self -> It is not possible to pass a variable by reference through getCurrencyConversionRates() as a parameter as changes made to the variable wont be saved. However, in JS you can simply initialize the variable outside of the function and modify it's value from within the function.
https://www.javascripttutorial.net/javascript-pass-by-value/
useEffect(() => {
const getCurrencyConversionRates=async()=>{
// console.log("Retrieving list of currency conversion rate");
await retrieveCurrencyConversionRateFunction();
//Gets new information every 6 seconds
intervalCall = setInterval(async() => {
// console.log("Getting new currency conversion rates!");
const currTime = new Date().toLocaleTimeString();
console.log("Time -> "+currTime);
await retrieveCurrencyConversionRateFunction();
}, 60*100);
//Have to wait until currencyConversionRateFunction is loaded before the map function is called on it in the render view otherwise will have error
setLoading(false);
// return intervalCall;
}
let intervalCall;
getCurrencyConversionRates();
//Clear interval
return () => {
console.log("CLEARING CURRENCY INTERVAL HERE");
console.log(intervalCall);
clearInterval(intervalCall);
};
},[]);
This may help
useEffect(() => {
let isValid = true;
let intervalCall;
const getCurrencyConversionRates = async () => {
console.log("Retrieving list of currency conversion rate");
await retrieveCurrencyConversionRateFunction();
// after await if the component unmounts, and this scope
// becomes stale, skip futher execution
// so the interval wont be started, and wont break in dev mode where useEffect runs twice
if (!isValid) { return; }
//Gets new information every 6 seconds
intervalCall = setInterval( async () => {
console.log("Getting new currency conversion rates!");
const currTime = new Date().toLocaleTimeString();
console.log("Time -> "+currTime);
await retrieveCurrencyConversionRateFunction();
// might want to check valid scope inside
// retrieveCurrencyConversionRateFunction
// by passing it a flag to skip execution on a unmounted component scope
}, 60*100);
//Have to wait until currencyConversionRateFunction is loaded before the map function is called on it in the render view otherwise will have error
setLoading(false);
}
getCurrencyConversionRates();
//Clear interval
return () => {
console.log("CLEARING CURRENCY INTERVAL HERE");
isValid = false
// if interval was not stared, dont clear it
if (intervalCall) { clearInterval(intervalCall); }
console.log(intervalCall);
};
},[]);
more about useEffect life cycle, to get an idea why
A new effect is created after every render
How the cleanup for previous effect occurs before executing of current useEffect
You can read about why isValid is set synchronizing with effects
If you are intererested in taking a deep dive, consider reading a blog post by Dan on useEffect, its old but explanation the details to build a good mental model about useEffects and functional components.
Hope it helps, cheers

How can I return empty the snapshot docs of firebase without returning undefined

So I just directly go to the code. Basically let say I have a carts and orders...The carts and orders should be in the item of a certain "email". Now what I want to do since my snapshot carts and orders loop around its item and some of its email is not the same whose email is login. I wonder how can I return empty the carts and orders if the login account has no yet "carts or orders" without returning "undefined" item.
onSnapshot(orderCollectionRef,snapshot => {
// snapshot.docs.map((doc_,idx) => {
// if (doc_.data().email == currentUser?.email) {
// setOrders(prevState => [...prevState,doc_.data()])
// }
// })
setOrders(snapshot.docs.map((doc_,idx) => {
if (doc_.data().email == currentUser?.email) {
return {
...doc_.data()
}
} else {
return null
}
}))
})
As you see I have a doc_.data().email == currentUser?.email because it is inside the authentication email...now since the orders have different located emails in their cart.. I wonder how to not make const [orders,setOrders] = useState([]) not have a list of undefined cause this is what returning at when their is no user login or there is no item in orders and carts...
You see also in the comment tags that those are most valid that do not return undefined since it was a push array,but it was not useful cause it is not removing the arrays when I update new carts rather it just make the call twice...basically what I mean here( I am talking too much) is
setOrders(snapshot.docs.map((doc_,idx) => {
if (doc_.data().email == currentUser?.email) {
return {
...doc_.data()
}
} else {
// What should will return here???
// return null
}
}))
How do I make this return just empty just none if there is no email that in the list of snapshot of carts and orders
It sounds like you want to filter out the items without a matching email address, and then only map the remaining ones, which you can do with Array.filter:
setOrders(snapshot.docs
.filter((doc_) => doc_.data().email == currentUser?.email)
.map((doc_) => doc_.data());
}))

compare an object inside a map function React

I try to render a mapped list of activity based on props. Layout of those props are:
completedActivity:
message:
status:
time:
type:
progressId:
time:
userId:
I need to compare the completedActivity.progressId to another set of props.
logData:
timestamp:
taskRunId:
userName:
I need to see where completedActivity.progressId = logData.taskRunId. If they are the same I need to grab the logData.userName.
Here is the code to render out the activities. It is working just, but I need to add additional info to them. As of right now it will render activity.userId, which is a bind user and not the actual user. If they are not the same, it needs to render the bind user.
const ActivityList = props => {
const activityList = props.data.completedActivity.map(activity => (
<li
class="collection-item"
onClick={() =>
activity.messageDetails
? props.toggleMessageDetails(true, activity.messageDetails)
: false
}
>
<p>{activity.type}</p>
<p className="message">{activity.message}</p>
{/*
displays who last ran that activity and when they did. Need to come up with a better naming solution.
*/}
<div class="whodiditandwhen">
<span>{activity.userId}
</span>
{/*<span>{activity.userId}</span>*/}
<span>{new Date(activity.time).toLocaleString()}</span>
</div>
{/*
this will allow the user to click on the icon and see more detailed info on the activity. NOT ALL ACTIVITES HAVE THIS
*/}
{activity.messageDetails}
</li>
));
return (
<ul className="activity-list">{activityList}
</ul>
);};
Here is a screenshot of how it is rendered now.
Thank you
This is what I tried:
const userNameLog = props.data.completedActivity.map(activity => {
let result = props.logData.find(log => log.taskRunID === activity.progressId)
let userName = ""
if(result === undefined){
userName = activity.userId
} else {
userName = result
}
console.log(userName)
}
)
This works to some degree except it gets rendered multiple times.
I was able to solve this issue inside of the axios call that is getting the data before the page renders it.
export function getActivityUpdates(options, updateProgress) {
axios
.post(URL, qs.stringify(options))
.then(response => {
// Null out saved currentActivityID if no live activity.
handleCurrentActivityStorage(response.data.liveActivity);
let changedIDs = [];
let dataResponse = response.data;
dataResponse.userId = ". . ."
/*
iterate over the completed activity and the log data.
compare progress id to taskRunId.
logData is from highest to lowest id.
completedActivity is also from high to low but puts parents above children.
check and make sure that it has a bind user written to it.
Then if taskrunid is = to or less than the progressId. change it and add it to the changedIDs array.
the ChangedIds array makes sure that ids are not iterated over multiple times.
set the userID to the actual username found in the logs.
*/
dataResponse.completedActivity.forEach(data => {
options.logData.forEach(log => {
//
if (
data.userId === options.bindUsername &&
!changedIDs.includes(data.progressId) &&
(log.taskRunID === data.progressId ||
log.taskRunID < data.progressId)) {
changedIDs.push(data.progressId)
data.userId = log.magUserName;
}
})
});
updateProgress(dataResponse);
// Exit function if a task is not running
if (!response.data.liveActivity.length) {
return;
}
// Get updated data every second until task is complete
setTimeout(
getActivityUpdates.bind(null, options, updateProgress),
1000
);
})
.catch(() => showErrorMessage(options.command));
}

how to get updated state value after updating in a function

I'm trying to get login user detail. I need to put data to state and used the same state for further processing it takes time to put data to state and use this data. nearly it takes 2- 3 sec to use the state with that data.
I solved this issue if we use setTimeOut() function with 3 sec so that we it updates data in this time. if we don't use setTimeOut() and use state wright after updating it then it will provide initial data of the state.
complete_date = date + ":" + month + ":" + year;
var query = firebase.database().ref('attendence/'+ complete_date +"/").orderByKey();
email_match=false;
entry_time =null,
l_id = null,
query.on("value",(data)=>
{
data.forEach(function(childs)
{
l_id = childs.key;
// __ to check if the user already loged in before.
var att_query = firebase.database().ref('attendence/'+ complete_date +"/"+ l_id).orderByKey();
att_query.once("value",(data)=>
{
if(email == data.child('email').val())
{
email_match = true;
entry_time = data.child('timeEntry').val();
}
}); //
}); //
this.setState({ last_id:l_id });
this.setState({ emailMatchState:email_match });
this.setState({alreayLogedState : entry_time});
}); // _____end of query.on("value",(data)=>
setTimeout(() =>
{
console.log("-----------------------------------");
console.log("already logged :",this.state.alreayLogedState," email match :",this.state.emailMatchState , " result : ", this.state.result , " last_id :",st.last_id);
console.log("-----------------------------------");
},3000);
I need to use state after updating without the use of setTimeout() for faster working of the application.
The second argument for setState is a callback to execute when all updates are done
this.setState({ last_id:l_id }, () => console.log(this.state.last_id))
setState takes an optional second argument that gets called after the updates to the state object have taken place.
example:
let callback = () => {
// do something
}
this.setState({
keyToUpdate: valueToUpdate
}, callback)
Additional reading about the use of this callback:
https://medium.learnreact.com/setstate-takes-a-callback-1f71ad5d2296?gi=18aa91e88437
Also, unless something is happening in between the seperate calls, these:
this.setState({ last_id:l_id });
this.setState({ emailMatchState:email_match });
this.setState({alreayLogedState : entry_time});
can be simplified to this:
this.setState({
last_id:l_id,
emailMatchState: email_match,
alreayLogedState: entry_time
}); //optionally add callback

UI-grid saveState service circular logic

Here is a summary of the problem: I set up a column sortChange() listener, which responds to sort changes by firing off a query to fetch newly sorted data. I save the grid state before the fetch, and restore the grid state after the fetch. The problem is that the restore gridState mechanism triggers the original sort listener, causing the whole process to start over again, and again, and again.
scope.sitesGrid.onRegisterApi = function(gridApi) {
scope.gridApi = gridApi;
scope.gridApi.core.on.sortChanged(scope, function () {
// load new sites on a sort change
scope.initialize();
});
};
scope.initialize = function() {
// save current grid state
scope.gridApi && (scope.gridState = scope.gridApi.saveState.save());
fetchSites().then(function (sites) {
scope.sitesGrid.data = sites
// restore current grid state, but inadvertently retrigger the 'sortChanged' listener
scope.gridApi.saveState.restore(scope,scope.gridState);
})
};
I was thinking that I could set up a click listener on each column header, instead of using a sortChange listener, however this solution seems ugly and requires going into every header cell template and making changes.
How about some kind of scope variable to track the loading of data?
scope.gridApi.core.on.sortChanged(scope, function () {
if (!scope.isLoading) {
scope.initialize();
}
});
and
fetchSites().then(function (sites) {
scope.isLoading = true;
scope.sitesGrid.data = sites;
scope.gridApi.saveState.restore(scope,scope.gridState);
scope.isLoading = false;
})
You might need to add some timeout() calls in places if there are timing issues with this. Creating a Plunker to demonstrate this would help in that case.
I think i find solution. I created restore function in my directive (u can use it where you want). I just block executing next iteration until action is finished.
function restoreState() {
if ($scope.gridState.columns !== undefined && !isRestoring) { //check is any state exists and is restored
isRestoring = true; //set flag
$scope.gridApi.saveState.restore($scope, $scope.gridState)
.then(function () {
isRestoring = false; //after execute release flag
});
}
}
function saveState() {
if (!isRestoring) {
$scope.gridState = $scope.gridApi.saveState.save();
}
}

Resources