Reputation: 2728
I tried to fetch docs from the firestore, it returns an empty array but when I run console.log(docs); outside the declared function, it returns the actual array. I know this error occurs because my useEffect function runs first before getting the docs from the firestore. I want to know how to fix this issue.
const Component = (props) => {
const { docs } = useFirestore('books');
const id = props.match.params.id;
const loadContent = () => {
const book = docs && docs.filter(doc => doc.id === id);
console.log(book); //not getting book from the docs because docs is empty
}
useEffect(() => {
async function getContent(){
await loadContent();
}
getContent()
},[]);
};
useFirestore.js
import { useState, useEffect } from 'react';
import { firestore } from '../config/fbConfig';
const useFirestore = (collection) => {
const [docs, setDocs] = useState([]);
const [loading, setLoading] = useState(true);
// getting realtime data from the firebase for books
useEffect(() => {
let unsubscribe = firestore.collection(collection)
// .orderBy('createdAt', 'desc')
.onSnapshot((querySnapshot) => {
const items = [];
querySnapshot.forEach((doc) => {
items.push({...doc.data(), id: doc.id});
});
setDocs(items);
setLoading(false);
});
return unsubscribe;
}, []); // eslint-disable-line react-hooks/exhaustive-deps
return { docs, loading };
}
export default useFirestore;
Upvotes: 0
Views: 640
Reputation: 6264
I think you are pretty close to getting the expected behaviour. Here's how I'd approach it:
const Component = props => {
const { docs } = useFirestore("books");
const id = props.match.params.id;
// useMemo - whenever `docs` change, recalculate the book variable.
// If `docs` don't change, `book` will also not change.
// Your `docs` will probably change on every snapshot.
const book = useMemo(() => docs && docs.filter(doc => doc.id === id), [docs]);
console.log(book);
useEffect(() => {
if (book) {
// Do something with the book, e.g. loadContent(book).
// Keep in mind that this will run on every snapshot.
// If you only want to run this once, you'll need an
// extra state variable to store that the effect was
// already run, and check it here.
}
}, [book]); // The effect will run whenever book changes
};
The useFirestore
hook looks almost fine, only one remark: right now even if you change the collection
parameter, the snapshot listener won't change. You might want to do this:
useEffect(() => {
const unsubscribe = firestore.collection(collection).onSnapshot(snapshot => {
const items = snapshot.map(doc => ({ ...doc.data(), id: doc.id }));
setDocs(items);
setLoading(false);
});
return unsubscribe;
// Whenever `collection` changes, `unsubscribe` will be called, and then this hook
// will subscribe to the new collection.
}, [collection]);
If you want the useFirestore
hook to only query for a particular book, you need to change the hook to accept and use a document ID, something like this:
const getDoc = doc => ({ ...doc.data(), id: doc.id });
const useFirestore = (collection, docId) => {
const [docs, setDocs] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
let subject = firestore.collection(collection);
if (docId) {
// If docId is available, listen for changes to a
// particular document
subject = subject.doc(docId);
}
const unsubscribe = subject.onSnapshot(snapshot => {
// Notice here that if listening for a particular docId,
// the snapshot will be that document, not an array.
// To maintain the interface of the hook, I convert that
// document to an array with a single item.
const items = docId ? [getDoc(doc)] : snapshot.map(getDoc);
setDocs(items);
setLoading(false);
});
return unsubscribe;
}, [collection, docId]);
return { docs, loading };
};
Upvotes: 1
Reputation: 2562
I need more informations on the 'useFirestore' code but you should at least write your code like that.
do not list every document on firestore to just get one (you pay for each read request)
load the document in useEffect, not outside
useEffect must depend on the id
const Component = (props) => {
const id = props.match.params.id;
const firestore = //;
const [book, bookSet] = useState(false);
useEffect(() => {
//Depending on useFirestore code
firestore.collections('books').doc(id)
.then( snapshot => {
if ( !snapshot.exists ) {
bookSet(null);
} else {
bookSet(snapshot.data());
}
});
}, [id]);
if( book === false) return <p>Loading</p>
if (!book) return <p>Not exists</p>
return <p>Display it</p>;
};
Edition
Here is my guess with your 'useFirestore' hooks
const Component = (props) => {
const id = props.match.params.id;
const { docs, loading } = useFirestore('books');
useEffect(() => {
if( loading) console.log('Loading');
return;
const book = docs && docs.filter(doc => doc.id === id);
console.log({book});
},[loading, docs, id]);
};
Upvotes: 1