React Firebase collection where and orderBy not working togehter - reactjs

I am using React NextJS and Firebase with it. For my application, I have a collection query which goes by
const userParticipantsRef = db.collections('participants')
.where(
"users",
"array-contains",
username,
).orderBy('updated', 'asc')
const [snapshot, loading] = useCollection(userParticipantsRef);
// snapshot?.docs -> Undefiend [if I use orderBy]
// snapshot?.docs -> Array<> [if I don't use orderBy]
I have made sure that there is an updated field in the document
Still returns undefined
snapshot?.docs returns undefined
If I don't use order, then it returns the array perfectly

Related

Firestore how to access subcollection inside docs map (Flutter)

I tried to access the subcollection 'match' using docs map.
final firestore = FirebaseFirestore.instance.collection('time').get();
firestore.docs.map((e) {
DateTime dt = e.data()['time'];
e.collection('match'); // How to access its subcollection
})
How can I access the subcollection 'match' on each document and at the same time accessing the 'time' field.
Your firestore variable is a Future<QuerySnapshot> object, and you're missing an await or then to wait for the future to resolve:
final firestore = FirebaseFirestore.instance.collection('time').get();
final snapshot = await firestore;
You can then access a subcollection of each time document with:
snapshot.docs.map((doc) {
...
var matchCollection = doc.reference.collection('match');
})
You might also want to look at using a collection group query to read/query all match collections in one go.

How to pass auth uid as a query parameter without getting undefined?

I am using react-query-firebase library to make a query to a document.
According to documentation this is how you make a query:
const ref = doc(firestore, 'users', id);
const product = useFirestoreDocument(["users", id], ref);
Everything works when I manually set id like this:
const id = "pW5CizOJOpXezr5lGGsh";
However I want to set the uid dynamically and I am using auth state hook as well:
const user = useAuthUser(["user"], auth);
const ref = doc(firestore, "users", user.data.uid);
the problem is that on page load user initializes as undefined and I need to wait for a split second for user.data.uid to populate and because of that I am getting TypeError: Cannot read property 'indexOf' of undefined
is there a way to skip making a query until user has finished loading and only then make a query.
with react-query, you would usually just disable the query until you have all the dependencies via the enabled property:
useQuery(["users", id], ref, { enabled: Boolean(id) }
This is documented in the dependent queries section of the docs.

How to debug this Firestore/React expression?

I don't understand the meaning of '.doc' and/or 'doc' in Firestore, and am thus unable to get my documentReference...
const getIt = () => {
setLoading(true);
const item = [];
const docRef = firebase
.firestore()
.collection("polja")
.doc("id", "==", match.params.id);
//
console.log(docRef);
//
docRef.onSnapshot((doc) => {
setItem(doc.data());
// //
setLoading(false);
});
};
You must use .doc() when you know the document ID and then the query goes as follows:
const docRef = firebase.firestore().collection("polja").doc("documentId");
But in case you are looking for documents with a specific field value, then you are looking for .where()
const docRef = firebase.firestore().collection("polja").where("id", "==", match.params.id);
This can match multiple documents where the value of that field matches the supplied value. So to control the number or results returned, use a limit clause or paginate your query.
If you use .doc() function, then the variable doc in the response is a DocumentSnapshot. You can then use doc.data() to get its data.
On the other hand, if you use .where(), it'll return FirebaseFirestore.QuerySnapshot<FirebaseFirestore.DocumentData> which contains all the documents matching the condition you had specified in the .where(). To access all the documents in that QuerySnapshot, you must access the docs property like this: snapshot.docs.
Now this will be an array of documents. You can use snapshot.size to see how many documents have matched your query.
Some practical examples to clarify:
//Collection in Firestore:
users -> {userID as documentId} -> user data in the document
//In this case you know the userID then you can should use the .doc()
const userDocRef = firebase.collection("users").doc("userId");
userDocRef.get().then((userData) => {
console.log(userData.data())
//^ this will contain data in side that document
})
//In this case, you want to find Firestore documents based on value of any field present in the document, lets say in this example if user's age is more than 15
const userDocsRef = firebase.collection("users").where("age", ">=", 15);
userDocsRef.get().then((usersData) => {
console.log(usersData.docs)
//^ this will log an array of documents which matched the condition of having the value of field age greater than or equal to 15
})

Why is currentUser.uid undefined

I'm trying to add information to the document of the user currently logged in.
I have the following code snippet of code in my component -
console.log("user", auth.currentUser?.uid);
useEffect(() => {
if (productId) {
db.collection("users")
.doc(auth.currentUser?.uid)
.collection("cart")
.doc(productId)
.onSnapshot((snapshot) => setProduct(snapshot.data()));
}
}, []);
Here,
const auth = firebase.auth();
The console log actually gives me the uid of the user but the hook below produces an errror -
FirebaseError: Function CollectionReference.doc() requires its first argument to be of type non-empty string, but it was: undefined
I have used the same approach in another component to add data and it works fine.
Why does this happen? Thanks in advance.
auth.currentUser is going to be null if there is no user signed in at the moment it was accessed. Your code is blindly ignoring this possibility by using the ? operator to "safely" access its properties anyway. When you use ?, if the prior expression is "falsy", the entire expression becomes undefined. Your code should instead check for null before assuming there is an object to use.
const currentUser = auth.currentUser
if (currentUser) {
const uid = currentUser.uid;
}
else {
// what do you want to do if there is no one signed in?
}
If you need to wait until a user is actually signed in, you should use an auth state observer to get a callback that tells you when the user object is available.
See also: Typescript the safe navigation operator ( ?. ) or (!.) and null property paths

Caching in React

In my react App I have a input element. The search query should be memoized, which means that if the user has previously searched for 'John' and the API has provided me valid results for that query, then next time when the user types 'Joh', there should be suggestion for the user with the previously memoized values(in this case 'John' would be suggested).
I am new to react and am trying caching for the first time.I read a few articles but couldn't implement the desired functionality.
You don't clarify which API you're using nor which stack; the solution would vary somewhat depending on if you are using XHR requests or something over GraphQL.
For an asynchronous XHR request to some backend API, I would do something like the example below.
Query the API for the search term
_queryUserXHR = (searchTxt) => {
jQuery.ajax({
type: "GET",
url: url,
data: searchTxt,
success: (data) => {
this.setState({previousQueries: this.state.previousQueries.concat([searchTxt])
}
});
}
You would run this function whenever you want to do the check against your API. If the API can find the search string you query, then insert that data into a local state array variable (previousQueries in my example).
You can either return the data to be inserted from the database if there are unknowns to your view (e.g database id). Above I just insert the searchTxt which is what we send in to the function based on what the user typed in the input-field. The choice is yours here.
Get suggestions for previously searched terms
I would start by adding an input field that runs a function on the onKeyPress event:
<input type="text" onKeyPress={this._getSuggestions} />
then the function would be something like:
_getSuggestions = (e) => {
let inputValue = e.target.value;
let {previousQueries} = this.state;
let results = [];
previousQueries.forEach((q) => {
if (q.toString().indexOf(inputValue)>-1) {
result.push(a);
}
}
this.setState({suggestions: results});
}
Then you can output this.state.suggestions somewhere and add behavior there. Perhaps some keyboard navigation or something. There are many different ways to implement how the results are displayed and how you would select one.
Note: I haven't tested the code above
I guess you have somewhere a function that queries the server, such as
const queryServer = function(queryString) {
/* access the server */
}
The trick would be to memorize this core function only, so that your UI thinks its actually accessing the server.
In javascript it is very easy to implement your own memorization decorator, but you could use existing ones. For example, lru-memoize looks popular on npm. You use it this way:
const memoize = require('lru-memoize')
const queryServer_memoized = memoize(100)(queryServer)
This code keeps in memory the last 100 request results. Next, in your code, you call queryServer_memoized instead of queryServer.
You can create a memoization function:
const memo = (callback) => {
// We will save the key-value pairs in the following variable. It will be our cache storage
const cache = new Map();
return (...args) => {
// The key will be used to identify the different arguments combination. Same arguments means same key
const key = JSON.stringify(args);
// If the cache storage has the key we are looking for, return the previously stored value
if (cache.has(key)) return cache.get(key);
// If the key is new, call the function (in this case fetch)
const value = callback(...args);
// And save the new key-value pair to the cache
cache.set(key, value);
return value;
};
};
const memoizedFetch = memo(fetch);
This memo function will act like a key-value cache. If the params (in our case the URL) of the function (fetch) are the same, the function will not be executed. Instead, the previous result will be returned.
So you can just use this memoized version memoizedFetch in your useEffect to make sure network request are not repeated for that particular petition.
For example you can do:
// Place this outside your react element
const memoizedFetchJson = memo((...args) => fetch(...args).then(res => res.json()));
useEffect(() => {
memoizedFetchJson(`https://pokeapi.co/api/v2/pokemon/${pokemon}/`)
.then(response => {
setPokemonData(response);
})
.catch(error => {
console.error(error);
});
}, [pokemon]);
Demo integrated in React

Resources