Mix useEffect and Firebase’s onValue

Rather than nest everything in your getGamesInSelectedGroup function (which is the pattern you would use for Promise-based APIs), just call it in the body of the useEffect to make managing the listener simpler:

useEffect(() => {
    if (!state.currentGroup) {
        return
    }

    const db = getDatabase();
    const resp = ref(db, `/games/${state.currentGroup.name}`);

    return onValue(resp, (snap) => { // <--- return the unsubscriber!
        if (snap.exists()) {
            const data = snap.val()
            const games = Object.keys(data)
                 .map(k => ({
                     id: k,
                     group: state.currentGroup.name,
                     ...data[k]
                 }));

            setState((prev) => ({
                ...prev,
                games, // you can use this instead of "games: games"
                isLoaded: true,
            }));
            return
        }
        setState((prev) => ({
            ...prev,
            games: null,
            isLoaded: true,
        }));
        toast.warning("no data for " + state.currentGroup.name)
    });
}, [state.currentGroup])

I would also recommend using a “snapshot to array of children” function rather than using Object.keys(snapshot.val()) to maintain the sort order from the query (it would be ignored using the code as-is). Unfortunately, at the time of writing, there is no equivalent of Firestore’s QuerySnapshot#docs for the RTDB just yet. But it’s pretty easy to make our own:

// returns array of DataSnapshot objects under this DataSnapshot
// put this outside of your component, like in a common function library file
const getSnapshotChildren = (snapshot) => {
    const children = [];
    // note: the curly braces on the next line are important! If the
    // callback returns a truthy value, forEach will stop iterating
    snapshot.forEach(child => { children.push(child) })
    return children;
}

useEffect(() => {
    if (!state.currentGroup) {
        return
    }

    const db = getDatabase();
    const resp = ref(db, `/games/${state.currentGroup.name}`);

    return onValue(resp, (snap) => { // <--- return the unsubscriber!
        if (snap.exists()) {
            const games = getSnapshotChildren(snap)
                 .map(child => ({
                     id: child.key,
                     group: state.currentGroup.name,
                     ...child.val()
                 }));

            setState((prev) => ({
                ...prev,
                games, // you can use this instead of "games: games"
                isLoaded: true,
            }));
            return
        }
        setState((prev) => ({
            ...prev,
            games: null,
            isLoaded: true,
        }));
        toast.warning("no data for " + state.currentGroup.name)
    });
}, [state.currentGroup])

CLICK HERE to find out more related problems solutions.

Leave a Comment

Your email address will not be published.

Scroll to Top