In order to show loading progress, I'm trying to wrap my onSnapshot call in a promise upon initial fetch. Data is loading correctly, but real-time updates are not functioning correctly.
Is there a way implement this type of functionality using the onSnapshot method?
Here's my initial data grab. Real-time updates functioned correctly before implementing the promise wrapper:
const [heroesArr, setHeroesArr] = useState([]);
const db = firebase.firestore();
const dbError = firebase.firestore.FirestoreError;
useEffect(() => {
const promise = new Promise((resolve, reject) => {
db.collection("characterOptions")
.orderBy("votes", "desc")
.onSnapshot(coll => {
const newHeroes = [];
coll.forEach(doc => {
const {
name,
votes
} = doc.data();
newHeroes.push({
key: doc.id,
name,
votes
});
});
if(dbError) {
reject(dbError.message)
} else {
resolve(newHeroes);
}
});
});
promise
.then(result => {
setHeroesArr(result);
})
.catch(err => {
alert(err);
});
}, [db]);
Again, data is being loaded to the DOM, but real-time updates are not functioning correctly.
In order to show loading progress, I'm trying to wrap my onSnapshot call in a promise upon initial fetch. Data is loading correctly, but real-time updates are not functioning correctly.
Is there a way implement this type of functionality using the onSnapshot method?
Here's my initial data grab. Real-time updates functioned correctly before implementing the promise wrapper:
const [heroesArr, setHeroesArr] = useState([]);
const db = firebase.firestore();
const dbError = firebase.firestore.FirestoreError;
useEffect(() => {
const promise = new Promise((resolve, reject) => {
db.collection("characterOptions")
.orderBy("votes", "desc")
.onSnapshot(coll => {
const newHeroes = [];
coll.forEach(doc => {
const {
name,
votes
} = doc.data();
newHeroes.push({
key: doc.id,
name,
votes
});
});
if(dbError) {
reject(dbError.message)
} else {
resolve(newHeroes);
}
});
});
promise
.then(result => {
setHeroesArr(result);
})
.catch(err => {
alert(err);
});
}, [db]);
Again, data is being loaded to the DOM, but real-time updates are not functioning correctly.
Share Improve this question asked Jun 21, 2019 at 6:24 mvkdiegnhoiwmvkdiegnhoiw 1621 gold badge2 silver badges12 bronze badges2 Answers
Reset to default 7onSnapshot is not really patible with promises. onSnapshot listeners listen indefinitely, until you remove the listener. Promises resolve once and only once when the work is done. It doesn't make sense to bine onSnapshot (which doesn't end until you say) with a promise, which resolves when the work is definitely plete.
If you want do get the contents of a query just once, just get() instead of onSnapshot. This returns a promise when all the data is available. See the documentation for more details.
Here's what I think going on with your code: When your ponent mounts, your promise gets executed once via useEffect and that's where you set state. However, subsequent updates via the onSnapshot listener are not going to change the db
reference, and therefore will not trigger useEffect again, and therefore will not execute the promise again, and therefore not set state again.
The only code that will execute when you receive a snapshot update is the callback function within .onSnapshot().
To fix this, I think you could try the following (I'm honestly not sure if it'll work, but worth a try):
- Create a variable to track the initial load:
let isInitialLoad = true;
- Inside your promise.then(), add
isInitialLoad = false;
- Inside your .onSnapshot(), add
if (!isInitialLoad) setHeroesArr(newHeroes);
– this way, on initial load setHeroesArr gets executed on the promise but on snapshot updates setHeroesAss gets executed in the .onSnapshot() callback
The downside to this approach is that setHeroesArr will be called immediately upon a snapshot change rather than being wrapped in a promise.
Hope this helps!