useEffect with firestore - javascript

I'm trying to do the following, i cannot get any errors but what's weird is, while setRivalGuess in the first condition setRivalGuess(doc.data().guess2) doesn't work, the second one setRivalGuess(doc.data().guess1) works really well. I checked database and everything stored well, that is, each data that I want to fetch is available on the database. I don't know whether it is about my way of using useEffect.
const { rivalGuess, setRivalGuess } = useGame();
const game = query(roomColRef, where("roomId", "==", roomId))
useEffect(() => {
const getUsers = async () => {
const data = await getDocs(game);
data.forEach((doc)=> {
if (doc.data().numberOfPlayers == 2 ){
if(userValue == doc.data().players[0].username)
if (doc.data().guess2 =! 0){
setRivalGuess(doc.data().guess2)}
if (userValue == doc.data().players[1].username)
if (doc.data().guess1 =! 0){
setRivalGuess(doc.data().guess1)} }})};
getUsers();
}, [ rivalGuess, setRivalGuess ])

rivalGuess was before global state , but know it's in the hook.
const UseRivals = (collectionStr) =>{
const [ rivalGuess, setRivalGuess ] =useState([])
const { roomId, userValue } = useGame()
useEffect(() => {
const collectionRef = collection(db, collectionStr);
const q = query(collectionRef, where("roomId", "==", roomId ))
const unSub = onSnapshot(q , (snapshot) => {
snapshot.docs.forEach(doc => {
if (doc.data().numberOfPlayers==2) {
if (userValue == doc.data().players[0].username) if (doc.data().guess2 =! 0)
{ setRivalGuess(doc.data().guess2) }
if (userValue == doc.data().players[1].username) if (doc.data().guess1 =! 0)
{ setRivalGuess(doc.data().guess1)}}})
}, (err) => {
console.log(err.message);
});
return () => unSub();
}, [collectionStr]);
return { rivalGuess };
}
export default UseRivals;

Related

setInterval keeps running even after clearInterval

Thanks in advance.
This is my question : It is Quiz website for couple.
Before partner finish quiz, It send get request to server every 5s.
but the problem is even partner's answers are set, setInterval never stops.
but if I refresh my website, It works well.
Can you please give me advise?
const postAnswers = useGetResults();
const postPartnerAnswers = useGetPartnerResults();
const [myResult, setMyResult] = useState<FinalAnswer | undefined>();
const [partnerResult, setPartnerResult] = useState<FinalAnswer | undefined>();
const [errorMessage, setErrorMessage] = useState<string | undefined>(undefined);
const [isLoading, setIsLoading] = useState<boolean>(false);
const init = async () => {
try {
const email = localStorage.getItem('email');
const partnerEmail = localStorage.getItem('partnerEmail');
if (email !== undefined && partnerEmail !== undefined) {
// localStorage에 이메일 값들이 있으면,
const result = await postAnswers(email, partnerEmail);
const otherResult = await postPartnerAnswers(email, partnerEmail);
if (result.answers !== undefined && otherResult.answers !== undefined) {
// 몽고디비에서 받아온 값이 둘다 있으면
setMyResult(result);
setPartnerResult(otherResult);
} else {
// 몽고디비에서 받아온 값이 없으면
console.log(result.answers, otherResult.answers);
setIsLoading(true);
}
}
} catch (error) {
setErrorMessage('로딩하는 도중 에러가 발생했습니다');
console.error(error);
}
};
useEffect(() => {
init();
}, []);
useEffect(() => {
if (myResult !== undefined && partnerResult !== undefined) {
setIsLoading(false);
console.log('둘다 값이 있어요!');
console.log(isLoading);
}
}, [myResult, partnerResult]);
const timer = () => {
return setInterval(() => {
init();
console.log('isLoading', isLoading);
if (isLoading === false) {
console.log('clear');
clearInterval(timer());
}
}, 5000);
};
useEffect(() => {
if (isLoading === true) {
console.log('둘다 값이 없어요!');
timer();
}
if (isLoading === false) {
console.log('clear');
clearInterval(timer());
}
}, [isLoading]);
deployed website : https://www.couple-quiz.com/
Expanding on #Ethansocal comment:
Your code is calling clearInterval(timer()) which will create a new interval that it will immediately clear. It seems that you are confusing the API of removeEventListener and clearInterval.
clearInterval should be called with the identifier returned by setInterval.
I suggest getting rid of the timer function and rewriting your last useEffect to make it return a cleanup function when isLoading is true:
useEffect(() => {
if (isLoading) {
console.log('둘다 값이 없어요!');
const interval = setInterval(init, 5_000);
return () => { clearInterval(interval) };
} else {
console.log('clear');
}
}, [isLoading]);

React Native Firestore: How do I listen for database changes at the same time as using the .where() query?

I have made a FlatList that gets populated from a firestore database. I can currently do all the CRUD operations, but when I edit an entry, it doesn't change in the FlatList. It does change in the firestore database.
I suspect it's because I'm not using .onSnapshot(). My problem is that I need to filter the data using .where() and I haven't been able to find out how to combine the two operations.
My code looks like this:
export const Coach = () => {
const navigation = useNavigation();
const [user, setUser] = useState();
const [userName, setUserName] = useState('');
const [workoutIds, setWorkoutIds] = useState([]);
const [workouts, setWorkouts] = useState([]);
const userRef = firestore().collection('Users');
const workoutRef = firestore().collection('Workouts');
// Setting the user state
auth().onAuthStateChanged(userInstance => {
if (userInstance) {
setUser(userInstance);
}
});
// Getting coach id's from firestore - Started out at individual workout id's
useEffect(() => {
if (user) {
const subscriber = userRef.doc(user.uid).onSnapshot(userSnap => {
if (userSnap) {
setUserName(userSnap.data().Name);
setWorkoutIds(userSnap.data().Workouts);
}
});
return () => subscriber();
}
}, [user]);
// using the list of coach id's to get workouts
useEffect(() => {
if (workoutIds.length != 0) {
let workoutList = [];
workoutRef
.where(firestore.FieldPath.documentId(), 'in', workoutIds)
.get()
.then(query => {
query.forEach(snap => {
workoutList.push({...snap.data(), key: snap.id});
});
setWorkouts(workoutList);
});
}
}, [workoutIds]);
The problem should lie in the last useEffect block.
So how do I get it to listen for changes and update the FlatList, while still using the .where()?
----------------------------------------- Edit -----------------------------------------
I have tried to add an onSnapshot to my query:
Before:
// using the list of coach id's to get workouts
useEffect(() => {
if (workoutIds.length != 0) {
let workoutList = [];
workoutRef
.where(firestore.FieldPath.documentId(), 'in', workoutIds)
.get()
.then(query => {
query.forEach(snap => {
workoutList.push({...snap.data(), key: snap.id});
});
setWorkouts(workoutList);
});
}
}, [workoutIds]);
After:
// using the list of coach id's to get workouts
useEffect(() => {
if (workoutIds.length != 0) {
let workoutList = [];
workoutRef
.where(firestore.FieldPath.documentId(), 'in', workoutIds)
.onSnapshot(query => {
query.forEach(snap => {
workoutList.push({...snap.data(), key: snap.id});
});
setWorkouts(workoutList);
});
}
}, [workoutIds]);
It still doesn't update the view straight away and now I get an error about encountering two of the same keys.
To solve the issue I had to add .onSnapshot() to my query for it to listen to changes in the database. On top of that I accidentally put the temporary list that I added objects to, outside the onSnapshot(), so it just kept adding on. After moving the temporary list into the onSnapshot(), it now updates.
Before:
useEffect(() => {
if (workoutIds.length != 0) {
let workoutList = [];
workoutRef
.where(firestore.FieldPath.documentId(), 'in', workoutIds)
.get()
.then(query => {
query.forEach(snap => {
workoutList.push({...snap.data(), key: snap.id});
});
setWorkouts(workoutList);
});
}
}, [workoutIds]);
After:
useEffect(() => {
if (workoutIds.length != 0) {
workoutRef
.where(firestore.FieldPath.documentId(), 'in', workoutIds)
.onSnapshot(query => {
let workoutList = [];
query.forEach(snap => {
workoutList.push({...snap.data(), key: snap.id});
});
setWorkouts(workoutList);
});
}
}, [workoutIds]);

Multiple axios get request not returning the data properly

I have created a react hook to work on with multiple get request using axios
const useAxiosGetMultiple = (urls,{preventCall = false} = {}) => {
const [error, setError] = useState(false);
const [loading, setLoading] = useState(false);
const [response, setResponse] = useState(()=>{
const temp = {}
Object.keys(urls).forEach(key => temp[key] = [])
return temp
})
const [reloadToken, setReloadToken] = useState(false)
const urlObj = useRef({...urls})
const unmountedOnReload = useRef(false)
useEffect(() => {
if(preventCall === true){
return null
}
let unmounted = false;
const source = axios.CancelToken.source();
setLoading(true)
const requests = []
Object.values(urlObj.current).forEach(url => {
requests.push(
axios.get(url, {
cancelToken: source.token,
})
);
});
const result = {}
const errors = {}
console.log(requests)
Promise.allSettled(requests)
.then(resArray => {
if(!unmounted){
console.log('from promise allsettled')
console.log(resArray)
console.log(urlObj.current)
Object.keys(urlObj.current).forEach((key,i) =>{
if(resArray[i].status === 'fulfilled'){
result[key] = resArray[i].value.data.responseData
}
if(resArray[i].status === 'rejected'){
errors[key] = resArray[i].reason
result[key] = []
}
})
setError(errors)
setLoading(false)
setResponse(result)
}
})
.catch(err => {
if (!unmounted) {
setError(err);
setLoading(false);
setResponse([])
if (axios.isCancel(err)) {
console.log(`request cancelled:${err.message}`);
} else {
console.log("another error happened:" + err.message);
}
}
})
return () => {
unmounted = true;
unmountedOnReload.current = true
source.cancel("Api call cancelled on unmount");
};
}, [reloadToken,preventCall]);
const reFetchAll = () => {
setReloadToken((token) => !token);
};
const reload = (urlKey) =>{
unmountedOnReload.current = false
setLoading(true)
axios.get(urls[urlKey])
.then(res =>{
if(!unmountedOnReload.current){
setLoading(false)
setResponse({...response,[urlKey]: res.data.responseData})
}
})
.catch(err=>{
if(!unmountedOnReload.current){
setLoading(false)
setError({...error, [urlKey]: err})
setResponse({...response,[urlKey]: []})
}
})
}
return {response, loading, error, reFetchAll, reload, setLoading};
};
I call this hook as follows..
const {response,loading,setLoading,reload} = useAxiosGetMultiple({
stateCodes: StateCode.api,
countryCodes: CountryCode.api,
districts: District.api,
})
Rather than getting variable stateCodes containing state codes or countryCodes containing country codes it's returning in wrong order or returning same data in multiple variable. Every time the call happens every time it changes. I also tried axios.all method instead of Promise.all but problem remains same.
Even in chrome's network panel the response data is improper.
What's the possible cause for this error and how to fix it ?
Thanks in advance

How to move the code to set ut DB and collection out from my file and just requre it?

So, let's say I have this code that works perfectly.
const {
Database
} = require("arangojs");
var db = new Database({
url: "http://localhost:8529"
});
const database_name = "cool_database";
db.useBasicAuth("username", "password123");
db.listDatabases()
.then(names => {
if (names.indexOf(database_name) > -1) {
db.useDatabase(database_name);
db.get();
} else {
db.createDatabase(database_name)
.then(() => {
db.useDatabase(database_name);
db.collection("my-collection").create();
});
}
});
const collection = db.collection("my-collection");
const getJobFromQueue = () => {
return db.query({
query: "FOR el IN ##collection FILTER DATE_TIMESTAMP(el.email.sendAfter) < DATE_NOW() AND el.status != 'processed' AND el.status != 'failed' SORT el.email.sendAfter LIMIT 1 RETURN el",
bindVars: {
"#collection": "my-collection"
}
})
.then(cursor => cursor.all());
}
But I want to move the top code out to another file and just require db and collection, how do I make that work? Have been struggling to make it work for too long now.
const {
db,
collection
} = require("./db");
const getJobFromQueue = () => {
return db.query({
query: "FOR el IN ##collection FILTER DATE_TIMESTAMP(el.email.sendAfter) < DATE_NOW() AND el.status != 'processed' AND el.status != 'failed' SORT el.email.sendAfter LIMIT 1 RETURN el",
bindVars: {
"#collection": "my-collection"
}
})
.then(cursor => cursor.all());
}
just do exactly what you proposed. move the upper part of your code to db.js and expose dband collection using exports:
db.js:
const {
Database
} = require("arangojs");
var db = new Database({
url: "http://localhost:8529"
});
const database_name = "cool_database";
db.useBasicAuth("username", "password123");
db.listDatabases()
.then(names => {
if (names.indexOf(database_name) > -1) {
db.useDatabase(database_name);
db.get();
} else {
db.createDatabase(database_name)
.then(() => {
db.useDatabase(database_name);
db.collection("my-collection").create();
});
}
});
exports.collection = db.collection("my-collection");
exports.db = db;
index.js:
const {
db,
collection
} = require("./db");
const getJobFromQueue = () => {
return db.query({
query: "FOR el IN ##collection FILTER DATE_TIMESTAMP(el.email.sendAfter) < DATE_NOW() AND el.status != 'processed' AND el.status != 'failed' SORT el.email.sendAfter LIMIT 1 RETURN el",
bindVars: {
"#collection": "my-collection"
}
})
.then(cursor => cursor.all());
}
WARNING:
keep in mind, there is a potential race condition in your code. you may end up using db and collection, before they hat been initialized.

Firestore cloud function to recursively update subcollection/collectionGroup

I have this cloud function:
import pLimit from "p-limit";
const syncNotificationsAvatar = async (
userId: string,
change: Change<DocumentSnapshot>
) => {
if (!change.before.get("published") || !change.after.exists) {
return;
}
const before: Profile = change.before.data() as any;
const after: Profile = change.after.data() as any;
const keysToCompare: (keyof Profile)[] = ["avatar"];
if (
arraysEqual(
keysToCompare.map((k) => before[k]),
keysToCompare.map((k) => after[k])
)
) {
return;
}
const limit = pLimit(1000);
const input = [
limit(async () => {
const notifications = await admin
.firestore()
.collectionGroup("notifications")
.where("userId", "==", userId)
.limit(1000)
.get()
await Promise.all(
chunk(notifications.docs, 500).map(
async (docs: admin.firestore.QueryDocumentSnapshot[]) => {
const batch = admin.firestore().batch();
for (const doc of docs) {
batch.update(doc.ref, {
avatar: after.avatar
});
}
await batch.commit();
}
)
);
})
];
return await Promise.all(input);
};
How can I recursively update the notifications collection but first limit the query to 1.000 documents (until there are not more documents) and then batch.update them? I'm afraid this query will timeout since collection could grow big over time.
Posting a solution I worked out, not following the context of the question though but it can easily be combined. Hope it helps someone else.
import * as admin from "firebase-admin";
const onResults = async (
query: admin.firestore.Query,
action: (batch: number, docs: admin.firestore.QueryDocumentSnapshot[]) => Promise<void>
) => {
let batch = 0;
const recursion = async (start?: admin.firestore.DocumentSnapshot) => {
const { docs, empty } = await (start == null
? query.get()
: query.startAfter(start).get());
if (empty) {
return;
}
batch++;
await action(
batch,
docs.filter((d) => d.exists)
).catch((e) => console.error(e));
await recursion(docs[docs.length - 1]);
};
await recursion();
};
const getMessages = async () => {
const query = admin
.firestore()
.collection("messages")
.where("createdAt", ">", new Date("2020-05-04T00:00:00Z"))
.limit(200);
const messages: FirebaseFirestore.DocumentData[] = [];
await onResults(query, async (batch, docs) => {
console.log(`Getting Message: ${batch * 200}`);
docs.forEach((doc) => {
messages.push(doc.data());
});
});
return messages;
};

Categories