React / Firebase : Sort querySnapshot push - javascript

I have the following:
const useItems = () => {
const user = firebase.auth().currentUser;
const user1 = user.uid;
const [items, setItems] = useState([]);
useEffect(() => {
const unsubscribe = firebase
.firestore()
.collection("items")
.where("User", "==", user1)
.orderBy("Category", "asc")
.get().then(function (querySnapshot) {
querySnapshot.forEach(function(doc) {
options.push({
value: doc.data().Name.replace(/( )/g, ''),
label: doc.data().Name,
Weight: doc.data().Weight
});
});
})
});
}
How can I sort the options.push by label ascending (a-z) in the querySnapshot?

Related

Firebase Firestore nested collection in Cloud Functions

I am trying to implement the nested query with Firestore in Cloud Functions but stumbled upon issues with reading values in a for loop. Are there ways to adjust the following code so I could do some operations after reading all records from a collection?
const firestore = admin.firestore();
const today = new Date();
const snap = await firestore
.collection('places')
.where('endDate', '<', today)
.get()
const userIds = [...new Set(snap.docs.map((doc: any) => doc.data().owner))];
const updatePromises = snap.docs.map((d: any) => {
return d.ref.update({
isPaid: false,
isActive: false
})
})
await Promise.all(updatePromises);
const userCol = firestore.collection('users');
const userDocs = await Promise.all(userIds.map(uid => userCol.doc(uid).get()));
const userData = userDocs.reduce((acc, doc) => ({
...acc,
[doc.id]: doc.data()
}), {})
snap.docs.forEach((l: any) => {
const ownerData = userData[l.owner];
const { email, displayName } = ownerData;
console.log(email, displayName);
const message = {
// Some values
}
return sendGrid.send(message);
})
return null;
{ owner: '<firebaseUid'>, address: 'Royal Cr. Road 234' }
{ email: 'asdfa#afsdf.com' }
<firebase_uid>: {
displayName: '',
email: '',
phoneNumber: ''
}
The userIds.push(owner); will keep adding duplicate values in that array and if a single user is owner of multiple locations, you'll end up querying same data multiple times. If you are trying to read owner's data along with a location, then try refactoring the code as shown below:
const firestore = admin.firestore();
const today = new Date();
const snap = await firestore
.collection('locations')
.where('isActive', '==', true)
.get()
const userIds = [...new Set(snap.docs.map(doc => doc.data().owner))];
const updatePromises = snap.docs.map((d) => {
return d.ref.update({
isPaid: false,
isActive: false
})
})
// update documents
await Promise.all(updatePromises);
const userCol = firestore.collection("users")
const userDocs = await Promise.all(userIds.map(uid => userCol.doc(uid).get()))
const userData = userDocs.reduce((acc, doc) => ({
...acc,
[doc.id]: doc.data()
}), {})
// To get data of a location's owner
// console.log(userData[ownerId])
snap.docs.forEach((l) => {
const ownerData = userData[l.owner]
// run more logic for each user
})
return null;

How to get an element inside of an array in a function?

Code below outputs an array of users stored in Firestore db. Each document have the same id of a user.
const [user] = useAuthState(auth);
const [userData, setUserData] = useState([]);
const usersDB = collection(firestore, "Users");
const getUsers = async () => {
const data = await getDocs(usersDB);
setUserData(data.docs.map((doc) => ({ ...doc.data(), id: doc.id })));
};
useEffect(() => {
getUsers();
}, []);
I want to access the document with same id as logged in user and check if isAdmin field is true or false using useAuthState to get user uid. How should I write the code for this?
According to your code you are using getDocs instead of getDoc.
Here's a sample code using useEffect and checking boolean isAdmin.
const [userData, setUserData] = useState([]);
const email = "test#xyz.com";
const password = "123Addw1113#";
const getUsers = async () => {
signInWithEmailAndPassword(auth, email, password)
.then((userCredential) => {
const user = userCredential.user;
setDoc(doc(db, "<collection>", user.uid), {
someField: "<data>",
})
.then(async () => {
const docRef = doc(db, "<collection>", user.uid);
const docSnap = await getDoc(docRef);
if (docSnap.exists() && docSnap.data().isAdmin === true) {
setUserData(docSnap.data());
console.log("isAdmin is True");
} else if (docSnap.exists() && docSnap.data().isAdmin === false) {
console.log("isAdmin is False");
} else {
console.log("No such document!");
}
});
})
.catch((error) => {
console.log(error)
});
}
useEffect(() => {
getUsers();
}, []);

how to convert this into a async function?

I want to be able to retrieve the users from the Firestore database and filter to find a match between the id of the current logged in user with the id of the user from the database. I am not able to do that because I can't figure out a way to change this to async function:
const [loggedUser, setLoggedUser] = useState([]);
const [data, setData] = useState([]);
useEffect(() => {
const getUserData = () => {
onSnapshot(collection(db, "users"), (snapshot) => {
let list = [];
snapshot.docs.forEach((doc) => {
list.push({ id: doc.id, ...doc.data() });
setData(list);
});
}, (err) => {
console.log(err);
});
}
getUserData();
}, [])
useEffect(() => {
const getLoggedUser = onAuthStateChanged(auth, (user) => {
if (user) {
const uid = user.uid;
console.log(uid);
if (data) {
const signedUser = data.filter((item) => item.id === uid);
setLoggedUser(signedUser);
} else {
console.log("no matching data")
}
} else {
console.log("no user found")
}
});
getLoggedUser();
}, [])
I want to be able to retrieve the users from the Firestore database and filter to find a match between the id of the current logged in user with the id of the user from the database.
You can use getDoc instead that'll only fetch the user's document and will cost you only 1 read. Currently you are reading the whole collection that'll cost you N reads where N is number of documents in the users collection.
You can use useEffect() just once and query Firestore when the auth state has been updated. Try refactoring the code as shown below:
import { getDoc, doc } from "firebase/firestore"
const [loggedUser, setLoggedUser] = useState([]);
const [data, setData] = useState([]);
useEffect(() => {
onAuthStateChanged(auth, (user) => {
if (user) {
const uid = user.uid;
console.log("User UID:", uid);
const snapshot = await getDoc(doc(db, "users", uid));
if (snapshot.exists) {
setLoggedUser(snapshot.data());
} else {
console.log("user document missing")
}
} else {
console.log("User not logged in")
}
});
}, [])

Nested firestore query in react

I have a firestore database with 2 collections: users and products. Users has a 'cart' field (type: array) which stores the uniqueName (field in products) of the products added to cart by a user. I fetched the 'cart' array and now, for each item, I want to fetch the product doc so I can access each product's individual fields.
*currentUserCart stores the elements from the cart array: strings with the unique product name based on which I do the query in fetchProducts().
While fetchUserCart() works, I followed the same logic for fetchProducts() but it doesnt work.
I tried this, but I get the correct products only if I click again wherever on the modal screen
export const Navbarr = ({items}) => {
const [user, error] = useAuthState(auth);
const [cartItems, setCartItems]=React.useState(0);
const [open, setOpen] = React.useState(false);
const [currentUserCart, setCurrentUserCart]=React.useState([]);
const [currentProducts, setCurrentProducts]=React.useState([]);
const handleOpen = () => {
fetchUserCart();
setOpen(true);
fetchProducts();
};
const fetchProducts=async()=>{
const products=[];
currentUserCart.forEach(async(item)=>{
const q = query(collection(db, "products"), where("uniqueName", "==", item));
const doc = await getDocs(q);
const data = doc.docs[0].data();
setCurrentProducts(currentProducts=>[...currentProducts, data]);
})
console.log(currentProducts);
}
const fetchUserCart = async() =>{
const q = query(collection(db, "users"), where("uid", "==", user?.uid));
const doc = await getDocs(q);
const data = doc.docs[0].data();
setCurrentUserCart(data.cart);
console.log('cart '+currentUserCart);
}
const handleClose = () => setOpen(false);
const fetchUserCartItems=async()=>{
const q = query(collection(db, "users"), where("uid", "==", user?.uid));
const doc = await getDocs(q);
const data = doc.docs[0].data();
let cartItemsClone=data.cartItems;
setCartItems(cartItemsClone);
}
React.useEffect(() => {
fetchUserCartItems();
fetchUserCart();
fetchProducts();
}, [user], [currentUserCart], [currentProducts]);

Firestore pagination with react-redux

I try to add pagination using firebase and react redux-toolkit. I get the logic but having trouble using it with redux.
At first I wanted to set lastDoc in redux state but I got error since it is an object.
Then I changed the way and started to keep id of last document in the state. But then I can't get the firebase doc itself
const lastDocRef = firestoreDB.doc(`catalog/${publishedBooks.lastDocId}`)
is not same with
const lastDoc = snap.docs[snap.docs.length-1];
I appreciate any help how to solve this.
import { createSlice, createAsyncThunk, createEntityAdapter } from '#reduxjs/toolkit';
import firebaseService from 'app/services/firebaseService';
const firestoreDB = firebaseService.firestoreDB;
export const getPublishedBooks = createAsyncThunk('adminApp/publishedBooks/getPublishedBooks',
async (params, { dispatch, getState }) => {
const promise = firestoreDB
.collection('catalog')
.orderBy('lastPublish', 'desc')
.limit(10)
.get()
.then(snap => {
const lastDoc = snap.docs[snap.docs.length-1];
dispatch(setLastDocId(lastDoc.id));
let books = [];
snap.forEach(bookDoc => {
const id = bookDoc.id;
const data = bookDoc.data();
const lastPublish = data.lastPublish.toDate().toISOString();
books.push({ ...data, id, lastPublish });
});
return books;
})
.catch(error => {
return {}
});
const result = await promise;
return result;
}
);
export const getPublishedBooksNext = createAsyncThunk('adminApp/publishedBooks/getPublishedBooksNext',
async (params, { dispatch, getState }) => {
const { publishedBooks } = getState().adminApp;
const lastDocRef = firestoreDB.doc(`catalog/${publishedBooks.lastDocId}`)
const promise = firestoreDB
.collection('catalog')
.orderBy('lastPublish', 'desc')
.startAfter(lastDocRef)
.limit(10)
.get()
.then(snap => {
const lastDoc = snap.docs[snap.docs.length-1];
dispatch(setLastDocId(lastDoc.id));
let books = [];
snap.forEach(bookDoc => {
const id = bookDoc.id;
const data = bookDoc.data();
const lastPublish = data.lastPublish.toDate().toISOString();
books.push({ ...data, id, lastPublish });
});
return books;
})
.catch(error => {
return {}
});
const result = await promise;
return result;
}
);
const publishedBooksAdapter = createEntityAdapter({});
const initialState = publishedBooksAdapter.getInitialState({
lastDocId: null
});
export const {
selectAll: selectPublishedBooks,
selectById: selectPublishedBookById,
selectTotal: selectPublishedBooksTotal
} = publishedBooksAdapter.getSelectors(state => state.adminApp.publishedBooks);
const publishedBooksSlice = createSlice({
name: 'adminApp/publishedBooks',
initialState,
reducers: {
resetPublishedBooks: (state, action) => initialState,
setLastDocId: {
prepare: doc => {
const payload = doc
return { payload };
},
reducer: (state, action) => {
state.lastDocId = action.payload;
}
},
resetLastDocId: {
prepare: () => {
const payload = null
return { payload };
},
reducer: (state, action) => {
state.lastDocId = action.payload;
}
},
},
extraReducers: {
[getPublishedBooks.fulfilled]: publishedBooksAdapter.setAll,
[getPublishedBooksNext.fulfilled]: publishedBooksAdapter.upsertMany
}
});
export const { resetPublishedBooks, setLastDocId, resetLastDocId } = publishedBooksSlice.actions;
export default publishedBooksSlice.reducer;
lastDocRef only returns the doc reference. You need to get the actual doc itself.
const lastDocRef = await firestoreDB.doc(`catalog/${publishedBooks.lastDocId}`).get();
And you should use await instead of then-catch for more readble code.
export const getPublishedBooksNext = createAsyncThunk('adminApp/publishedBooks/getPublishedBooksNext',
async (params, { dispatch, getState }) => {
const { publishedBooks } = getState().adminApp;
try {
const lastDocRef = await firestoreDB.doc(`catalog/${publishedBooks.lastDocId}`).get();
const snap = await firestoreDB
.collection('catalog')
.orderBy('lastPublish', 'desc')
.startAfter(lastDocRef)
.limit(10)
.get()
const lastDoc = snap.docs[snap.docs.length-1];
let books = [];
dispatch(setLastDocId(lastDoc.id));
snap.forEach(bookDoc => {
const id = bookDoc.id;
const data = bookDoc.data();
const lastPublish = data.lastPublish.toDate().toISOString();
books.push({ ...data, id, lastPublish });
});
return books;
} catch (error) {
return {}
}
}
);
Edit: You can also save the lastDoc to redux then reference it later to avoid additional workload fetching for the lastDocRef.
export const getPublishedBooksNext = createAsyncThunk('adminApp/publishedBooks/getPublishedBooksNext',
async (params, { dispatch, getState }) => {
const { lastDocRef } = getState().adminApp; // get saved lastDoc
try {
const snap = await firestoreDB
.collection('catalog')
.orderBy('lastPublish', 'desc')
.startAfter(lastDocRef) // use it here.
.limit(10)
.get()
const lastDoc = snap.docs[snap.docs.length-1];
let books = [];
// dispatch(setLastDocId(lastDoc.id)); // instead of saving the doc id
dispatch(setLastDoc(lastDoc)); // save the last document instead
snap.forEach(bookDoc => {
const id = bookDoc.id;
const data = bookDoc.data();
const lastPublish = data.lastPublish.toDate().toISOString();
books.push({ ...data, id, lastPublish });
});
return books;
} catch (error) {
return {}
}
}
);

Categories