Home > Enterprise >  React and firebase v9: dynamically setting path on onSnapshot doesn't work
React and firebase v9: dynamically setting path on onSnapshot doesn't work

Time:11-28

Right now for my webapp I'm trying to use firebase to render data from my database onto my app. One of the users (users is a collection in the database) has a id of QTKVV0WOBMhkq7Q6TPpDsGvprXf1. The id is just a users id (so currentUser.uid). And each user has a collection of habits. When I hardcode the id QTKVV0WOBMhkq7Q6TPpDsGvprXf1 into the path I am able to display the data from my database on my app in localhost, but I want to be able to display any user's data regardless.

I've tried as many ways as I can but I just can't seem to figure it out. Any suggestions? I've tried making a variable that contains the currentUser.uid but that doesn't work. I've tried doing other stuff but it all ended in errors...

So ideally, i'd like to swap out the middle of the path in onSnapshot so that it will just take in the current.uid

import React, { useEffect, useState } from "react";
import Habit from "./Habit";
import SearchBar from "./SearchBar";
import db, { useAuth } from "../firebase";
import { onSnapshot, collection } from "@firebase/firestore";

const HabitList = ({ mainSection, handleMainSection }) => {
  // this initial state will be replaced with API request
  const [habits, setHabits] = useState([
    // { name: "Sample habit 1", id: 1 },
    // { name: "Sample habit 2", id: 2 },
    // { name: "Sample habit 3", id: 3 },
  ]);

  const currentUser = useAuth();
  var currentUserPath;
  if(currentUser) {
    console.log('uid: ', currentUser.uid)
    currentUserPath=currentUser.uid;
    console.log('currentUser: ', currentUser);
  }
    
  useEffect(
    () => 
    onSnapshot(collection(db, `users/QTKVV0WOBMhkq7Q6TPpDsGvprXf1/user_habits`), (snapshot) => 
      setHabits(snapshot.docs.map((doc) => doc.data()))
      //setHabits(snapshot.docs.map((doc) => doc.data())); // make sure that setHabits works and sets snapshot to habits
      //console.log(habits); // habits should have the habits from firebase, not the initial habits we hardcoded
        ), 
      []
    );
    
  return (
    <div className="flex flex-col">
      <SearchBar />
      {habits.map(h => (
        <Habit
          habitName={h.name}
          handleMainSection={handleMainSection}
          key={h.id}
        />
      ))}
    </div>
  );
};

export default HabitList;

firebase.js (useAuth)

// Import the functions you need from the SDKs you need
import { useEffect, useState } from "react";
import { initializeApp } from "firebase/app";
import { getAuth, createUserWithEmailAndPassword, signInWithEmailAndPassword, onAuthStateChanged } from "firebase/auth";
import { getFirestore } from "firebase/firestore";
import { doc, setDoc } from "firebase/firestore";
import { v4 as uuidv4 } from "uuid";
// TODO: Add SDKs for Firebase products that you want to use
// https://firebase.google.com/docs/web/setup#available-libraries

// Your web app's Firebase configuration
const firebaseConfig = {
  apiKey: "AIzaSyBN30k6RivLOuz7KToi_uD8V5s5cmyD9RM",
  authDomain: "auth-development-62c42.firebaseapp.com",
  projectId: "auth-development-62c42",
  storageBucket: "auth-development-62c42.appspot.com",
  messagingSenderId: "414005826367",
  appId: "1:414005826367:web:7b987851735426ebedf98a"
};

// Initialize Firebase
const app = initializeApp(firebaseConfig);
const auth = getAuth();

export function signup(email, password) {
  return createUserWithEmailAndPassword(auth, email, password);
}

export function login(email, password) {
  return signInWithEmailAndPassword(auth, email, password);
}

// eventually write a logout function

export async function sendHabitToFirestore(uidPath, habitName) {
  const db = getFirestore();
  const habitId = uuidv4();
  const pathDocRef = doc(db, "users", uidPath, "user_habits", habitId);
  await setDoc(pathDocRef, {
    name: habitName, 
    id: habitId
  });
}

export function useAuth() {
  const [currentUser, setCurrentUser ] = useState();
  useEffect(() => {
    const unsub = onAuthStateChanged(auth, user => setCurrentUser(user));
    return unsub;
  }, [])

  return currentUser;
}

export default getFirestore();

Error:

Failed to compile
./src/components/HabitList.jsx
SyntaxError: C:\Users\jojoc\Desktop\local_dev\habitude\src\components\HabitList.jsx: Unexpected token (37:6)

  35 |         setHabits(newHabits); // consider using snapshot.docChanges() in later renders for efficiency
  36 |         console.log("New version of habits found!", newHabits); // note: habits isn't updated straight away, so we use the array passed to setHabits
> 37 |       ),
     |       ^
  38 |       (error) => {
  39 |         // TODO: Handle errors!
  40 |       }
This error occurred during the build time and cannot be dismissed.

CodePudding user response:

Depending on your implementation of useAuth, currentUser may be briefly null which means on first render, your useEffect attaches to users/undefined/user_habits instead of the user ID you are expecting. Because your useEffect listener doesn't listen for changes to currentUser, it won't ever be called again with the proper user ID once the sesssion gets validated.

useEffect(() => {
  if (currentUser == null) { // signed out/not ready
    // if habits is already empty, don't trigger a rerender
    setHabits(habits.length === 0 ? habits : []);
    return;
  }
 
  const userDocRef = collection(db, `users/${currentUser.uid}/user_habits`);
  return onSnapshot(
    userColRef,
    (snapshot) => {
      const newHabits = snapshot.docs.map((doc) => doc.data()); 
      setHabits(newHabits); // consider using snapshot.docChanges() in later renders for efficiency
      console.log("New version of habits found!", newHabits); // note: habits isn't updated straight away, so we use the array passed to setHabits
    ),
    (error) => {
      // TODO: Handle errors!
    }
  );
}, [currentUser]); // rerun if currentUser changes (e.g. validated, signed in/out)
  • Related