You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

修复说明

  1. 核心问题原因:

    • 登录时setIsLoggedIn(true)先执行,导致App组件在email还未更新时就挂载,此时创建了路径为/的无效监听。
    • 未移除旧的onValue监听,数据库变更时多个监听同时触发,包含路径错误的旧监听。
  2. 具体修复步骤:

    • App.js:
      • 移除无效的readFromDatabase导入。
      • 新增email为空的判断,避免创建无效监听。
      • 保存onValue返回的 unsubscribe 函数,在组件卸载或依赖变化时移除旧监听,防止内存泄漏和重复触发。
    • index.js:
      • 调整handleLogin执行顺序:先设置email再标记登录状态,确保App挂载时email已有值。
      • 仅当email不为空时渲染App,避免无效挂载。
    • Habit.jsx:
      • 修复history函数的重复key问题。
      • 将逻辑与运算符&改为&&,修正条件判断错误。

内容的提问来源于stack exchange,提问作者Lukas007

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 17:44:54