Reputation: 61
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)}/>
Upvotes: 2
Views: 638
Reputation: 4600
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>
)
}
Upvotes: 1
Reputation: 61
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>
}
Upvotes: 0