skip to Main Content

When I am trying to change the data – the changes are getting to my Firestore Database, it’s ok, but when I reload the page or logging out and trying to log in again – the user data does not appear in my project, but is also stored in my Firestore Database. How to save and display user data in the project even after I reload the page?

This is my code

    const [email, setEmail] = useState('')
    const [phone, setPhone] = useState('')
    const [language, setLanguage] = useState('')
    const [valute, setValute] = useState('')
    const [instagram, setInstagram] = useState('')
   

    const {user} = UserAuth()


    const createUser = (email, password) => {
        createUserWithEmailAndPassword(auth, email, password)
        .then((userCredential) => {
            const user = userCredential.user;
            const uid = user.uid;
            setDoc(doc(db, 'users', uid), {
                email: email,
                name: name,
                password: password,
                phone: phone,
                language: language,
                instagram: instagram,
                valute: valute,
            });
          })
          .catch((error) => {
            const errorCode = error.code;
            const errorMessage = error.message;
            // ..
          });
    }

    const updatePerson = async(email, phone, language, valute, instagram, name, ) => {
      await updateDoc(doc(db, 'users', user.uid), {email, phone, language, valute, instagram, name})
    }

    <input type="text" placeholder={name || user.displayName || user.email || user.phoneNumber} className='form-control' onChange={(e) => setName(e.target.value)}/>
    <input type="text" placeholder={instagram} className='form-control' onChange={(e) => setInstagram(e.target.value)}/>
    <input type="text" placeholder={email} className='form-control' onChange={(e) => setEmail(e.target.value)}/>
    <input type="text" placeholder={phone} className='form-control' onChange={(e) => setPhone(e.target.value)}/>
    <input type="text" placeholder={language} className='form-control' onChange={(e) => setLanguage(e.target.value)}/>
    <input type="text" placeholder={valute} className='form-control' onChange={(e) => setValute(e.target.value)}/>

2

Answers


  1. Chosen as BEST ANSWER

    I've found the answer on my question.

    We are expecting that onSnapshot will magically be called right after our app loads - it needs some time to fetch document from our collection. I would add additional flag which indicate if data is still loading or not:

    function useUserData(userId) {
      const [userData, setUserData] = useState(undefined);
      const [isLoading, setIsLoading] = useState(true);
    
      useEffect(() => {
        if (!userId) return; // null or undefined is passed, so just do nothing
    
        const unsubscribe = onSnapshot(doc(db, "users", userId), (doc) => {
          setIsLoading(false);
          if (doc.exists()) {
            setUserData(doc.data()); // found and setting a user
          } else {
            setUserData(null); // null means user not found in users collection
          }
        });
    
        return () => unsubscribe();
      }, [userId]);
    
      return { userData, isLoading };
    }
    

    Then use it like that:

    function Profile() {  /// or props here
     const { isLoading, userData } = useUserData(user.uid); /// if you use props just simple write uid
     if (isLoading) {
      return <span>Loading...</span>;
     }
     if (!userData) {
      return <span>Ooops. Profile not found</span>;
     }
     
     return <span>{userData.firstName}</span>
    }
    

  2. Im using custom hooks for that purpose. That is pretty reusable and i can load multiple (specific) users (or anything else) in same time.

    useUserData.jsx

    import { useEffect, useState } from "react";
    import { doc, onSnapshot } from "firebase/firestore";
    import { db } from "../firebase";
    
    
    export function useUserData(userId) {
      const [userData, setUserData] = useState(undefined); // undefined means not loaded
    
      useEffect(() => {
        if (!userId) {
          // null or undefined is passed
          setUserData(undefined);
          return; 
        }
        const unsubscribe = onSnapshot(doc(db, "users", userId), (doc) => {
          if (doc.exists()) {
            setUserData(doc.data()); // found and setting a user
          } else {
            setUserData(null); // null means user not found in users collection
          }
        });
    
        return () => unsubscribe();
      }, [userId]);
    
      return userData;
    }
    

    Usage and additional explanations and examples

    import React, { useState, useCallback, useEffect } from "react"
    import useAuthContext from "../../Context/AuthContext";
    import { useUserData } from "../../Hooks/useUserData";
    
    export function Home(props) {
      const {user, updateData} = useAuthContext();
    
      // NOTE: Do not destructure in this way due to it will cause an exception for null and undefined values
      // const {firstName, lastName} = useUserData(user?.uid); 
    
      const userData = useUserData(user?.uid); // user might be undefined, so ? is used. Null will be passed but hook will work
    
      // I prefer storing form data in this way, look at handleChanges function
      // Important: this approach relies on input "name" attribute, it should match
      const [formData, setFormData] = useState({
        firstName: "",
        lastName: ""
      });
    
      // Just to update the existing doc.
      const updatePersonSubmit = useCallback(async (evt) => {
        evt.preventDefault();
        await updateData(formData.firstName, formData.lastName);
      }, [updateData, formData]);
    
      // In setter you can get previous (current) value
      // this function that is passed as a parameter should return new object to set
      // new object consists of all ...prev fields it had
      // and one field is overriden by key (name) and will have a new value here
      const handleChange = useCallback((evt) => {
        setFormData(prev => ({...prev, [evt.target.name]: evt.target.value}));
      }, []);
    
      // listen to the userData updates and update the inputs
      // || prev.XXX is used due to data in userData can be null or undefined
      // inputs in React expects empty strings for bindings, nulls or undefined will
      // break everything, so using old value if any (initialized with "")
      useEffect(() => {
        setFormData(prev => ({
          ...prev,
          firstName: userData?.firstName || prev.firstName,
          lastName: userData?.lastName || prev.lastName
        }));
      }, [userData])
    
      return (
        <div className="container-fluid">
          {user && (
            <form>
              <p>Those fields are NOT in firestore, they needs to be set/updated with auth functions, not so easy here</p>
              <p>displayName: {user.displayName || "null"}</p>
              <p>email: {user.email || "null"}</p>
              <p>phoneNumber: {user.phoneNumber || "null"}</p>
            </form>
          )}
          <br/>
          <br/>
          <form onSubmit={updatePersonSubmit}>
            <p>Those fields are in firestore, they can be set/updated with firestore setDoc or updateDoc</p>
    
            <input name="firstName" type="text" placeholder={"firstName"} className='form-control'
                   value={formData.firstName} onChange={handleChange}/>
    
            <input name="lastName" type="text" placeholder={"lastName"} className='form-control'
                   value={formData.lastName} onChange={handleChange}/>
    
            <button type="submit">Save</button>
          </form>
        </div>
      )
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search