Cuando trabajo con Firebase y React , para obtener datos basados en cambios de estado o en cambios internos de la base de datos (de otro usuario, por ejemplo), a menudo confío en fragmentos de código como este:
useEffect(() => { const getGamesInSelectedGroup = () => { if (!state.currentGroup) { return } const db = getDatabase(); const resp = ref(db, `/games/${state.currentGroup.name}`); onValue(resp, (snap) => { 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: games, isLoaded: true, })); return } setState((prev) => ({ ...prev, games: null, isLoaded: true, })); toast.warning("no data for " + state.currentGroup.name) }) } getGamesInSelectedGroup(); }, [state.currentGroup]) Sin embargo, me pregunto si, cada vez que cambia state.currentGroup , se crea un nuevo oyente para /games/${state.currentGroup.name} . Si es así, ¿hay algún medio para cancelar la suscripción al oyente anterior antes de crear uno nuevo?
He pensado en reemplazar onValue por una llamada de get , todavía condicionada por state.currentGroup y usando onValue fuera de useEffect para reflejar el cambio "interno" de la base de datos.
En lugar de anidar todo en su función getGamesInSelectedGroup (que es el patrón que usaría para las API basadas en Promise), simplemente llámelo en el cuerpo de useEffect para simplificar la administración del oyente:
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]) También recomendaría usar una función de "instantánea a la matriz de niños" en lugar de usar Object.keys(snapshot.val()) para mantener el orden de clasificación de la consulta (se ignoraría usando el código tal como está). Desafortunadamente, en el momento de escribir este artículo, todavía no existe un equivalente de QuerySnapshot#docs de Firestore para RTDB. Pero es bastante fácil hacer el nuestro:
// 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])