我为此目的使用自定义挂钩。这是非常可重用的,我可以同时加载多个(特定)用户(或其他任何内容)。
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;
}
用法和附加解释和示例
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>
)
}