React中Firebase实时数据库更新时路径重置问题求助
问题描述
登录后,App.js可正常获取对应用户的习惯数据,但当Firebase实时数据库中的数据发生任何变更时,数据读取路径会被重置为index.js中useState的默认空值,导致读取整个数据库而非用户专属路径。页面刷新后功能恢复正常,直到数据库再次产生变更。预期行为是始终从编码后的邮箱路径读取数据。
代码文件
App.js
import React from "react"; import { useState, useEffect, useCallback } from "react"; import { db } from './firebase'; import { set, ref, onValue } from "firebase/database" import Habit from './Habit.jsx' import './App.css' function App({ email }) { const [habits, setHabits] = useState([]); const [habitName, setHabitName] = useState(''); const [isLoading, setIsLoading] = useState(false); const encodedEmail = email.replace(/@/g, '_at_').replace(/\./g, '_dot_'); console.log(encodedEmail) const readFromDatabase = useCallback(() => { if (!email || isLoading) return; setIsLoading(true); const dbRef = ref(db, `/${encodedEmail}`); console.log("first runs", encodedEmail) const unsubscribe = onValue(dbRef, (snapshot) => { const data = snapshot.val(); console.log('Data:', data); console.log(`Reading from path: /${encodedEmail}`); console.log(email, "email") if (data !== null) { const newHabits = Object.keys(data).map(key => ({ name: key, ...data[key] })); setHabits(newHabits); } setIsLoading(false); }); return unsubscribe; }, [encodedEmail, email, isLoading]); useEffect(() => { const unsubscribe = readFromDatabase(); return () => unsubscribe?.(); }, [readFromDatabase]); const writeToDatabase = () => { const cDate = new Date().toDateString() set(ref(db, `/${encodedEmail}/${habitName}`), { Name: habitName, Creation: cDate, Last: cDate, Repeated: 0 }); setHabitName(' ') } return ( <div className="App"> <div> {habits.map((habit) => { return ( <Habit habit={habit} key={habit.Name} encodedEmail={encodedEmail} /> ) })} </div> <input type="text" value={habitName} onChange={(e) => setHabitName(e.target.value)} required /> <button onClick={(e) => writeToDatabase()} className="createButton">Create</button> </div> ); } export default App;
login.jsx
import React, { useState } from 'react'; import './login.css' import { signInWithEmailAndPassword } from 'firebase/auth'; import { createUserWithEmailAndPassword } from 'firebase/auth'; import { auth } from './firebase'; const Login = ({ onLogin }) => { const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); const login = (e) => { e.preventDefault(); signInWithEmailAndPassword(auth, username, password) .then((userCredential) => { console.log(userCredential); onLogin(username) }) }; const signup = (e) => { e.preventDefault(); createUserWithEmailAndPassword(auth, username, password) .then((userCredential) => { console.log(userCredential); onLogin(username) }) }; const [isSignUp, setIsSignUp] = useState(false); const handleSubmit = (e) => { e.preventDefault(); if (isSignUp) { signup(e); } else { login(e); } console.log(username) }; return ( <div className='loginContainer'> <div className="card"> <div className="card-header"> <h2>Account</h2> </div> <div className="card-body"> <form onSubmit={handleSubmit}> <label htmlFor="email">Email:</label> <input type="email" id="email" name="email" value={username} onChange={(e) => setUsername(e.target.value)} required /> <label htmlFor="password">Password:</label> <input type='password' id="password" name='password' value={password} onChange={(e) => setPassword(e.target.value)} required /> <div className="card-footer"> <button type="submit" className="form-button" onClick={(e) => setIsSignUp(false)}>Login</button> <button type="submit" className='form-button' onClick={(e) => { setIsSignUp(true); handleSubmit(e); }}>Sign Up</button> </div> </form> <div className='change-type'> </div> </div> </div> </div> ); }; export default Login;
index.js
import React, { useState } from 'react'; import ReactDOM from 'react-dom'; import App from './App'; import Login from './login'; import './firebase' function Parent() { const [email, setEmail] = useState(""); return ( <Main onLogin={setEmail}> {email && <App email={email} />} </Main> ); } function Main({ onLogin, children }) { const [isLoggedIn, setIsLoggedIn] = useState(false); const handleLogin = (mail) => { console.log('Logging in with email:', mail); onLogin(mail); setIsLoggedIn(true); }; return ( isLoggedIn ? children : <Login onLogin={handleLogin} /> ); } ReactDOM.render(<Parent />, document.getElementById('root'));
Habit.jsx
import React from "react"; import { set, ref } from "firebase/database" import { db } from './firebase'; const Habit = ({ habit, encodedEmail }) => { const date = new Date().toDateString(); const lastDate = new Date(habit.Last); const lastIsToday = (lastDate - new Date(date)) >= 0; const updateHabit = () => { if (!lastIsToday && habit.Name) { set(ref(db, `/${encodedEmail}/${habit.Name}`), { Name: habit.Name, Creation: habit.Creation, Last: date, Repeated: habit.Repeated + 1 }); } } const history = () => { return ( Array.from({ length: habit.Repeated }).map((_, index) => ( <div key={`${habit.Name}-${index}`} className="square"></div> )) ); } return ( <div className="habit"> <div className="main"> <div className="name"> <strong> {habit.Name} </strong> </div> <div className="square" onClick={updateHabit} style={lastIsToday ? { backgroundColor: "green" } : { backgroundColor: "red" }}> </div> <div className="habitHistory"> {history()} </div> </div> <hr /> </div > ) } export default Habit;
控制台输出
jarmaralukas123_at_gmail_dot_com App.js:20 first runs jarmaralukas123_at_gmail_dot_com App.js:23 Data: {" Meditate": {…}, " Workout": {…}} App.js:24 Reading from path: /jarmaralukas123_at_gmail_dot_com App.js:25 jarmaralukas123@gmail.com email App.js:14 jarmaralukas123_at_gmail_dot_com 4App.js:14 jarmaralukas123_at_gmail_dot_com App.js:23 Data: {" Meditate": {…}, " Workout": {…}, test: {…}} App.js:24 Reading from path: /jarmaralukas123_at_gmail_dot_com App.js:25 jarmaralukas123@gmail.com email App.js:23 Data: {jarmaralukas123_at_gmail_dot_com: {…}, lukas_dot_jarmara_at_gmail_dot_com: {…}} App.js:24 Reading from path: / App.js:25 email App.js:14 jarmaralukas123_at_gmail_dot_com
修复说明
核心问题原因:
- 登录时
setIsLoggedIn(true)先执行,导致App组件在email还未更新时就挂载,此时创建了路径为/的无效监听。 - 未移除旧的
onValue监听,数据库变更时多个监听同时触发,包含路径错误的旧监听。
- 登录时
具体修复步骤:
- App.js:
- 移除无效的
readFromDatabase导入。 - 新增
email为空的判断,避免创建无效监听。 - 保存
onValue返回的 unsubscribe 函数,在组件卸载或依赖变化时移除旧监听,防止内存泄漏和重复触发。
- 移除无效的
- index.js:
- 调整
handleLogin执行顺序:先设置email再标记登录状态,确保App挂载时email已有值。 - 仅当
email不为空时渲染App,避免无效挂载。
- 调整
- Habit.jsx:
- 修复
history函数的重复key问题。 - 将逻辑与运算符
&改为&&,修正条件判断错误。
- 修复
- App.js:
内容的提问来源于stack exchange,提问作者Lukas007
相关产品推荐
相关产品推荐

