Complex search query in firebase

Viewed 474

I am new to React Native and Firebase and I am building an app using firestore.

the structure of firestore db looks like this :

AllPosts > Doc(each post) > Field(name,desc, .... , userId="12345")

and for users

users > doc(each user) id 12345 > field(name,desc,etc)

I don't know if this is the best way to setup the database. But when I go to a post screen, I need to load all data for that post & data for the userId who created the post, yet they are in entirely different collections.

I couldn't find a good way to do this. I've tried setting the userId field from post doc to a const 'fetchUserById' and perform a query based on that, but I think it's dead wrong

    const [post, setPost] = useState([]);
    const [user, setUser] = useState(null);

        useEffect(() => {
    
            firebase.firestore()
            .collection("allPosts")
            .where(firebase.firestore.FieldPath.documentId(), '==', props.route.params.postId)
            .get()
            .then((snapshot) => {
                let data = snapshot.docs.map(doc => {
                    const data = doc.data();
                    const id = doc.id;
                    return { id, ...data }
                })
                setPost(data)
            })

            const fetchUserById = post.userId;

            firebase.firestore()
            .collection('users')
            .where(fetchUserById, '==', firebase.firestore.FieldPath.documentId())
            .get()
            .then((snapshot) => {
                if (snapshot.exists) {
                    setUser(snapshot.data());
                }
                else {
                    
                }
            })
           
            
    
        },[])
2 Answers

since you store document id in post.userId you can just select document directly by id like that:

 firebase.firestore()
            .collection('users')
            .doc(post.userId)
            .get()
            .then((snapshot) => {
                if (snapshot.exists) {
                    setUser(snapshot.data());
                }
                else {
                    
                }
            })

the reason you are getting undefined is not an object is because post is not yet set within this execution of the useEffect. The flow is following:

  1. Component render method is called
  2. post is set to default value of [] (it seems that you do not expect a collection of posts so it should probably not be [])
  3. useEffect() callback is called
  4. you fetch allPosts from from firestore
  5. you call setPost(data)
    • this triggers state update and component is re-rendered
  6. in the re-rendered component the post variable is now set to collection of posts but in the useEffect() callback which is still running the post variable is still the same (empty array).
  7. fetching user fails because post.userId is undefined

You have multiple options how to solve it:

  1. you can store post.userId in a local variable inside of the useEffect() callback
  2. you can have a separate useEffect with dependency on post.

I like the first approach better, you should also not use where condition when you need a single document by id. Overall your code should be like that (I think, don't have anywhere to test it right now):

const [post, setPost] = useState(null); // you don't need an array here. You get 1 post and 1 user for it.
const [user, setUser] = useState(null);

    useEffect(() => {
        firebase.firestore()
        .collection("allPosts")
        .doc(props.route.params.postId) // no need to use `where`
        .get()
        .then((snapshot) => {
            const postData =snapshot.data();
            setPost(postData) // getting single document, no need to iterate over results
            // you cannot fetch user before you know its id so your next query should be inside of `then` callback 
            firebase.firestore()
                    .collection('users')
                    .doc(postData.userId)
                    .get()
                    .then((snapshot) => {
                        if (snapshot.exists) {
                            setUser(snapshot.data());
                        }
                    })
        })
    },[props.route.params.postId]) // you probably want to re-fetch if postId changes

If you know the document ID then you shouldn't use the where method.

firebase.firestore().collection('users')
    .doc(props.route.params.postId)
    .get()
    .then((snapshot) => {
        if (snapshot.exists) {
            setUser(snapshot.data());
        } else {
                    
        }
    })

The get() method returns a DocumentSnapshot.

Same goes for the second query but just a note, first parameter in .where() is the field name or path you are looking for and the third parameter is the value.

@param fieldPath — The path to compare

@param opStr — The operation string (e.g "<", "<=", "==", ">", ">=").

@param value — The value for comparison
Related