I have code as below.
I need to break the loop when first match is found.
const [isCodeValid, setIsCodeValid] = useState(false);
for (let i = 0; i < properyIds.length; i++) {
if (isCodeValid) {
break; // this breaks it but had to click twice so state would update
}
if (!isCodeValid) {
firestore().collection(`properties`)
.doc(`${properyIds[i]}`)
.collection('companies').get()
.then(companies => {
companies.forEach(company => {
if (_.trim(company.data().registrationCode) === _.trim(registrationCode.toUpperCase())) {
console.log("should break here")
// updating state like this wont take effect right away
// it shows true on second time click. so user need to click twice right now.
setIsCodeValid(true);
}
});
})
}
}
state won't update right away so if (!isCodeValid) only works on second click.
Once I find match I need to update state or variable so I can break the for loop.
I tried to use a variable but its value also not changing in final if condition, I wonder what is the reason? can anyone please explain ?
You should try and rewrite your code such that you will always call setIsCodeValid(value) once. In your case it could be called multiple times and it might not get called at all
const [isCodeValid, setIsCodeValid] = useState(false);
function checkForValidCode() {
// map to an array of promises for companies[]
const companiesPromises = properyIds.map(propertyId =>
firestore()
.collection(`properties`)
.doc(propertyId)
.collection('companies').get())
Promise.all(companiesPromises)
// flatten the 2d array to single array, re-create to JS array because of firestores internal types?
.then(companiesArray => [...companiesArray].flatMap(v => v))
// go through all companies to find a match
.then(companies =>
companies.find(
company => _.trim(company.data().registrationCode) === _.trim(registrationCode.toUpperCase())
))
.then(foundCompany => {
// code is valid if we found a matching company
setIsCodeValue(foundCompany !== undefined)
})
}
Try something like this:
import { useState } from 'react';
function YourComponent({ properyIds }) {
const [isCodeValid, setIsCodeValid] = useState(false);
async function handleSignupClick() {
if (isCodeValid) {
return;
}
for (let i = 0; i < properyIds.length; i++) {
const companies = await firestore()
.collection(`properties`)
.doc(`${properyIds[i]}`)
.collection('companies')
.get();
for (const company of companies.docs) {
if (_.trim(company.data().registrationCode) === _.trim(registrationCode.toUpperCase())) {
setIsCodeValid(true);
return;
}
}
}
}
return (<button onClick={handleSignupClick}>Sign Up</button>);
}
If you await these checks, that will allow you to sequentially loop and break out with a simple return, something you can't do inside of a callback. Note that if this is doing database queries, you should probably show waiting feedback while this is taking place so the user knows that clicking did something.
Update:
You may want to do all these checks in parallel if feasible so the user doesn't have to wait. Depends on your situation. Here's how you'd do that.
async function handleSignupClick() {
if (isCodeValid) {
return;
}
const allCompanies = await Promise.all(
properyIds.map(id => firestore()
.collection(`properties`)
.doc(`${properyIds[i]}`)
.collection('companies')
.get()
)
);
setIsCodeValid(
allCompanies.some(companiesSnapshot =>
companiesSnapshot.docs.some(company =>
_.trim(company.data().registrationCode) === _.trim(registrationCode.toUpperCase())
)
)
);
}
Can you not break it after setIsCodeValid(true);?
Use some:
companies.some(company => {
return _.trim(company.data().registrationCode) === _.trim(registrationCode.toUpperCase());
});
If some and forEach are not available then companies is not an array but an array-like object. To iterate through those, we can use for of loop:
for (const company of companies){
if (_.trim(company.data().registrationCode) === _.trim(registrationCode.toUpperCase())) {
// do something
break;
}
}
I tired below and it worked for me to break the loop.
I declared and tried to change this variable let codeValid and it was just not updating its value when match found. (not sure why)
But all of a sudden I tried and it just works.
I didnt change any actual code except for variable.
let codeValid = false;
let userInformation = []
for (let i = 0; i < properties.length; i++) {
console.log("called")
const companies = await firestore().collection(`properties`)
.doc(`${properties[i].id}`)
.collection('companies').get()
.then(companies => {
companies.forEach(company => {
if (_.trim(company.data().registrationCode) === _.trim(registrationCode.toUpperCase())) {
// a += 1;
codeValid = true;
userInformation.registrationCode = registrationCode.toUpperCase();
userInformation.companyName = company.data().companyName;
userInformation.propertyName = properties[i].propertyName;
}
});
})
if (codeValid) {
break;
}
}
Related
The problem is that every time I click on an element with a state things appear twice. For example if i click on a button and the result of clicking would be to output something in the console, it would output 2 times. However in this case, whenever I click a function is executed twice.
The code:
const getfiles = async () => {
let a = await documentSpecifics;
for(let i = 0; i < a.length; i++) {
var wrt = document.querySelectorAll("#writeto");
var fd = document.querySelector('.filtered-docs');
var newResultEl = document.createElement('div');
var writeToEl = document.createElement('p');
newResultEl.classList.add("result");
writeToEl.id = "writeto";
newResultEl.appendChild(writeToEl);
fd.appendChild(newResultEl);
listOfNodes.push(writeToEl);
listOfContainers.push(newResultEl);
wrt[i].textContent = a[i].data.documentName;
}
}
The code here is supposed to create a new div element with a paragraph tag and getting data from firebase firestore, will write to the p tag the data. Now if there are for example 9 documents in firestore and i click a button then 9 more divs will be replicated. Now in total there are 18 divs and only 9 containing actual data while the rest are just blank. It continues to create 9 more divs every click.
I'm also aware of React.Strictmode doing this for some debugging but I made sure to take it out and still got the same results.
Firebase code:
//put data in firebase
createFileToDb = () => {
var docName = document.getElementById("title-custom").value; //get values
var specifiedWidth = document.getElementById("doc-width").value;
var specifiedHeight = document.getElementById("doc-height").value;
var colorType = document.getElementById("select-color").value;
parseInt(specifiedWidth); //transform strings to integers
parseInt(specifiedHeight);
firebase.firestore().collection("documents")
.doc(firebase.auth().currentUser.uid)
.collection("userDocs")
.add({
documentName: docName,
width: Number(specifiedWidth), //firebase-firestore method for converting the type of value in the firestore databse
height: Number(specifiedHeight),
docColorType: colorType,
creation: firebase.firestore.FieldValue.serverTimestamp() // it is possible that this is necessary in order to use "orderBy" when getting data
}).then(() => {
console.log("file in database");
}).catch(() => {
console.log("failed");
})
}
//get data
GetData = () => {
return firebase.firestore()
.collection("documents")
.doc(firebase.auth().currentUser.uid)
.collection("userDocs")
.orderBy("creation", "asc")
.get()
.then((doc) => {
let custom = doc.docs.map((document) => {
var data = document.data();
var id = document.id;
return { id, data }
})
return custom;
}).catch((err) => {console.error(err)});
}
waitForData = async () => {
let result = await this.GetData();
return result;
}
//in render
let documentSpecifics = this.waitForData().then((response) => response)
.then((u) => {
if(u.length > 0) {
for(let i = 0; i < u.length; i++) {
try {
//
} catch(error) {
console.log(error);
}
}
}
return u;
});
Edit: firebase auth is functioning fine so i dont think it has anything to do with the problem
Edit: This is all in a class component
Edit: Clicking a button calls the function createFileToDb
I think that i found the answer to my problem.
Basically, since this is a class component I took things out of the render and put some console.log statements to see what was happening. what i noticed is that it logs twice in render but not outside of it. So i took the functions out.
Here is the code that seems to fix my issue:
contain = () => {
const documentSpecifics = this.waitForData().then((response) => {
var wrt = document.getElementsByClassName('writeto');
for(let i = 0; i < response.length; i++) {
this.setNewFile();
wrt[i].textContent = response[i].data.documentName;
}
return response;
})
this.setState({
docs: documentSpecifics,
docDisplayType: !this.state.docDisplayType
})
}
As for creating elements i put them in a function so i coud reuse it:
setNewFile = () => {
const wrt = document.querySelector(".writeto");
const fd = document.querySelector("#filtered-docs");
var newResultEl = document.createElement('div');
newResultEl.classList.add("result");
var wrtEl = document.createElement('p');
wrtEl.classList.add("writeto");
fd.appendChild(newResultEl);
newResultEl.appendChild(wrtEl);
}
The firebase and firestore code remains the same.
the functions are called through elements in the return using onClick.
I have this div that perform an onclick event by increasing the value of an upvote when a user click on the div. It increment which is fine, but I only want it to increment only once even when the user clicks on the div multiple times.
Here is my code
btnUpvote(data) {
let feeds = [...this.state.feeds]
let feed = feeds.find(x => x.id === data.id)
// feed.upvote +1
let get = feed.upvote + 1
console.log(get)
if (feed.upvote !== get) {
}
this.setState({
feeds
})
}
The value of the feed.upvote is stored in an array of object, any help would be appreciated.
Try adding this condition :
let get = 0
if(!feed.upvote){
get= feed.upvote + 1}
You could use an array to store the id which has already been upvoted.
Check it and handle your clicks accordingly.
let allowOneClicks = [];
function allowOnce(id, callback) {
if (allowOneClicks.includes(id)) return;
allowOneClicks.push(id);
callback();
}
In your case, it can be like this.
// outside your component;
const upvotedIds = [];
btnUpvote(data) {
let feeds = [...this.state.feeds]
let feed = feeds.find(x => x.id === data.id)
if (upvotedIds.includes(id)) return;
upvotedIds.push(id);
// upvote here.
}
you can try this.
onHandleClick = (id) => {
this.setState({
feed: {
...this.state.feed,
[id]: (this.state.feed[id] || 0) + 1
}
})
}
I am trying to find index of array using lodash locationbar. but my react console showing some warnings. can be resolve?
let wishListData = wishList.result;
let j = 0; const jMax = wishListData.length;
for (; j < jMax; j++) {
var index = _.findIndex(products.result, function (product) {
return product.id === wishListData[j]['pid']
});
if (index !== -1) {
products.result[index]['isWishList'] = true;
}
}
Iterate over wishList.result with forEach instead of a for loop, and you'll avoid the warning:
wishListData.forEach(({ pid }) => {
const index = _.findIndex(products.result, ({ id }) => id === pid);
if (index !== -1) {
products.result[index].isWishList = true;
}
});
Note that this is a linter warning, not a Javascript error. Your code works, the linter just considers it to be confusing - better to use array methods instead of loops when possible.
Also, feel free to remove the _ library and just use built-in Javascript methods instead, if you want:
wishListData.forEach(({ pid }) => {
const product = products.result.find(({ id }) => id === pid);
if (product) {
product.isWishList = true;
}
});
Or, for an O(N) solution instead of an O(N^2) solution, figure out all pids first, then iterate over the products:
const pids = new Set(wishListData.map(({ pid }) => pid));
products.result.forEach((product) => {
if (pids.has(product.id)) {
product.isWishList = true;
}
});
You can try something like this as well:
Instead of mutating product in products.result[index]['isWishList'] = true;, you should create new object to minimize side-effect.
Also, instead of looping on wishListData, you can create a list of PIDs and just check index. If this list is created outside, you can create list of PIDs outside as well. That will reduce processing it every time
const wishListPID = wishList.result.map((x) => x.pid);
const result = products.result.map((product) => {
const isWishList = wishListPID.indexOf(product.id) !== -1;
return { ...product, isWishList }
});
I'm just starting to figure out React by putting together a bit of code from different parts, and from an online course.
I'm using React, Next and Axios to get an API from a cryptocurrency server.
The main issue I'm facing is:
I am able to console.log(coinObjects) under getInitialProps, and it displays the object correctly
Despite this, coinObjects does not get rendered in {this.props.coinObjects}
As a possible clue, linksArr does get rendered in {this.props.linksArr}
The code I have is as follows:
class MainIndex extends Component {
static async getInitialProps(props) {
// setup - empty array and list of coins
const coinList = ["NEO", "ETH", "BTC"];
const numCoins = coinList.length;
const coinObjects = [];
const linksArr = [];
const isServer = typeof window === "undefined";
// API GET
const baseUrl = "https://min-api.cryptocompare.com/data/histohour?";
for (let coinName of coinList) {
linksArr.push(
baseUrl.concat("fsym=", coinName, "&tsym=", "USD", "&limit=", "3")
);
}
const getObj = async linksArr => {
try {
let res = await axios.all(linksArr.map(l => axios.get(l)));
for (let i = 0; i < linksArr.length; i++) {
coinObjects[coinList[i]] = res[i].data.Data;
}
} catch (err) {
console.error(err);
}
};
await getObj(linksArr);
console.log(coinObjects);
// Return updated arrays
if (isServer) {
return { coinObjects, numCoins, linksArr };
} else {
return {};
}
}
render() {
return (
<Layout>
<h2>
CoinObject has {this.props.coinObjects.length} coins
// Returns 0
<br />
LinksArr has {this.props.linksArr.length} links
// Returns 3
</h2>
</Layout>
);
}
}
Could anyone please help me? I've exhausted all the Google searches, Stackoverflow posts and coding friends that I can find (just 1). I can't figure out what's wrong, and I hope that this isn't a silly question because I've been tweaking and changing things extensively, but have yet to figure out what's wrong.
Here the coinObject is set to an array:
const coinObjects = [];
But later is treated as an Object:
coinObjects[coinList[i]] = res[i].data.Data;
That means that you would want to add to the array like this:
for (let i = 0; i < linksArr.length; i++) {
let data = res[i].data.Data;
let name = coinList[i];
coinObjects.push({ name: name, data: data });
}
Every minute I have a script that push a new record in my firebase database.
What i want is delete the last records when length of the list reach a fixed value.
I have been through the doc and other post and the thing I have found so far is something like that :
// Max number of lines of the chat history.
const MAX_ARDUINO = 10;
exports.arduinoResponseLength = functions.database.ref('/arduinoResponse/{res}').onWrite(event => {
const parentRef = event.data.ref.parent;
return parentRef.once('value').then(snapshot => {
if (snapshot.numChildren() >= MAX_ARDUINO) {
let childCount = 0;
let updates = {};
snapshot.forEach(function(child) {
if (++childCount <= snapshot.numChildren() - MAX_ARDUINO) {
updates[child.key] = null;
}
});
// Update the parent. This effectively removes the extra children.
return parentRef.update(updates);
}
});
});
The problem is : onWrite seems to download all the related data every time it is triggered.
This is a pretty good process when the list is not so long. But I have like 4000 records, and every month it seems that I screw up my firebase download quota with that.
Does anyone would know how to handle this kind of situation ?
Ok so at the end I came with 3 functions. One update the number of arduino records, one totally recount it if the counter is missing. The last one use the counter to make a query using the limitToFirst filter so it retrieve only the relevant data to remove.
It is actually a combination of those two example provided by Firebase :
https://github.com/firebase/functions-samples/tree/master/limit-children
https://github.com/firebase/functions-samples/tree/master/child-count
Here is my final result
const MAX_ARDUINO = 1500;
exports.deleteOldArduino = functions.database.ref('/arduinoResponse/{resId}/timestamp').onWrite(event => {
const collectionRef = event.data.ref.parent.parent;
const countRef = collectionRef.parent.child('arduinoResCount');
return countRef.once('value').then(snapCount => {
return collectionRef.limitToFirst(snapCount.val() - MAX_ARDUINO).transaction(snapshot => {
snapshot = null;
return snapshot;
})
});
});
exports.trackArduinoLength = functions.database.ref('/arduinoResponse/{resId}/timestamp').onWrite(event => {
const collectionRef = event.data.ref.parent.parent;
const countRef = collectionRef.parent.child('arduinoResCount');
// Return the promise from countRef.transaction() so our function
// waits for this async event to complete before it exits.
return countRef.transaction(current => {
if (event.data.exists() && !event.data.previous.exists()) {
return (current || 0) + 1;
} else if (!event.data.exists() && event.data.previous.exists()) {
return (current || 0) - 1;
}
}).then(() => {
console.log('Counter updated.');
});
});
exports.recountArduino = functions.database.ref('/arduinoResCount').onWrite(event => {
if (!event.data.exists()) {
const counterRef = event.data.ref;
const collectionRef = counterRef.parent.child('arduinoResponse');
// Return the promise from counterRef.set() so our function
// waits for this async event to complete before it exits.
return collectionRef.once('value')
.then(arduinoRes => counterRef.set(arduinoRes.numChildren()));
}
});
I have not tested it yet but soon I will post my result !
I also heard that one day Firebase will add a "size" query, that is definitely missing in my opinion.