带有 Firebase 用户的 React 应用程序不会保持注销状态/错误显示用户信息

React app with Firebase User wont stay signed out / displaying user info wrong

当前用户的

Console.logs

我有一个 UserContext.js 文件,其中包含所有 useContext 挂钩。 Nav.js 显示一些用户信息和“登录”或“退出”按钮。 a Profile.js 显示用户信息。 App.js 包含组件和 useContext。

发生的事情是,如果我与用户一起登录,所有内容都会显示,我可以导航到各个页面,并且用户信息会保持显示。如果我从导航菜单中单击“注销”,则所有用户信息都不会显示“无用户信息”,而是会在所有页面上显示空白字段。 “注销”按钮再次出现。我认为这是因为每次我导航到不同的页面时,将我所有组件包装在 App.js 中的 UserContextProvider 都从 toggleUser 函数调用 auth.signout?我可以将 toggleUser 函数提取到 UserContextProvider 的范围之外吗?还是发生了其他事情?

App.js

import './App.css';
import 'bootstrap/dist/css/bootstrap.min.css';

import React from "react";
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import { UserContextProvider } from '../../../src/util/Context/UserContext'

import Navmenu from '../Nav/Nav';
import Home from '../../Pages/Home/Home';
import Signin from '../../Pages/Sign-in/Sign-in';
import Signup from '../../Pages/Sign-up/Sign-up';
import Profile from '../../Pages/Profile/Profile';
import Footer from '../Footer/Footer';

const App = () => {
  return (
    <div className="App">
      <BrowserRouter>
        <UserContextProvider>
          <Navmenu />
          <hr className="p-0 m-0"></hr>
          <Routes>
            <Route exact path='/' element={<Home />} />
            <Route path='/signin' element={<Signin />} />
            <Route path='/signup' element={<Signup />} />
            <Route path='/profile' element={<Profile />} />
          </Routes>
          <Footer />
        </UserContextProvider>
      </BrowserRouter>
    </div>
  )
};

export default App;

UserContext.js

import React, { useContext, useState, useEffect } from 'react';
import { auth, createUserProfileDocument } from "../firebase/firebase.utils";

const UserContext = React.createContext(null);
const UserUpdateContext = React.createContext();

export const useUserContext = () => {
    // useContext hook 
    return useContext(UserContext);
}

export const useUserContextUpdate = () => {
    // useContext hook 
    return useContext(UserUpdateContext)
}

export const UserContextProvider = ({ children }) => {
    const [currentUser, setUser] = useState(null);
    let unsubscribeFromAuth = null;

    useEffect(() => {
        unsubscribeFromAuth = auth.onAuthStateChanged(async userAuth => {
            if (userAuth) {
                const userRef = await createUserProfileDocument(userAuth);

                userRef.onSnapshot(snapShot => {
                    setUser({
                        id: snapShot.id,
                        ...snapShot.data()
                    });
                });
            } else {
                setUser({ currentUser: userAuth })
            }
        });

        return () => {
            unsubscribeFromAuth();
        };
    }, [])
    

    const toggleUser = () => {
        auth.signOut()
        .then((currentUser) => {
            setUser(null)
        })
        .catch(e => console.log('There was a error:'(e)))
    }

    return (
        <UserContext.Provider value={currentUser} >
            <UserUpdateContext.Provider value={toggleUser} >
                {children}
            </UserUpdateContext.Provider >
        </UserContext.Provider >
    )
};

Nav.js

import React from 'react';
import { Link } from 'react-router-dom';
import './Nav.css';

import { useUserContext, useUserContextUpdate } from '../../../src/util/Context/UserContext';
import { auth } from '../../util/firebase/firebase.utils';

import { Nav, Navbar, NavDropdown } from 'react-bootstrap';
import Bell from './img/Bell.svg';
import Message from './img/Message.svg';
import Userprofile from './img/Userprofile.svg';
import Aboutme from './img/Aboutme.svg';
import Findfriends from './img/Findfriends.svg';
import Accountsetting from './img/Accountsetting.svg';

const Navmenu = ({  }) => {
    const currentUser = useUserContext();
    const toggleUser = useUserContextUpdate();

    return (
        <div className='tc f3'>
            <Navbar bg='light' expand='lg'>
                <a className="text-decoration-none" href="/">
                    <Navbar.Brand className="mx-2 mx-lg-5">Yelp-Clone</Navbar.Brand>
                </a>
                <Navbar.Toggle aria-controls="basic-navbar-nav" />
                <Navbar.Collapse id="basic-navbar-nav">
                    <hr></hr>
                    <Nav className='ml-5'>
                        <Nav.Link className='link-font' href="#home">Write a Review</Nav.Link>
                        <Nav.Link className='link-font' href="#link">Events</Nav.Link>
                        <Nav.Link className='link-font' href="#link">Talk</Nav.Link>
                    </Nav>
                    <div className="d-flex flex-row justify-content-lg-center align-items-center ml-auto">
                        <img className='nav-img me-2' src={Message}></img>
                        <img className='nav-img me-2' src={Bell}></img>
                        <img className='nav-img-lrg' src={Userprofile}></img>
                        <NavDropdown className='custom-dropdown-class me-3' id="basic-nav-dropdown">
                            <div className="d-flex flex-row">
                                <img className='nav-img-sml me-2' src={Userprofile}></img>
                                <div className="d-flex flex-column">
                                    {
                                        currentUser ?
                                            <span>{currentUser.displayName}</span>
                                            :
                                            <span>No User Found</span>
                                    }
                                    {
                                        currentUser ?
                                            <span>{currentUser.email}</span>
                                            :
                                            <span>No Email Found</span>
                                    }
                                </div>
                            </div>
                            <NavDropdown.Divider />
                            <div className="link-wrapper my-1">
                                <img className="link-font-sml me-2 inline-block" src={Aboutme}></img><a className="link-font-sml"><Link className="link-font-sml" to="/profile">About Me</Link></a>
                            </div>
                            <div className="link-wrapper my-1">
                                <img className="link-font-sml me-2 inline-block" src={Findfriends}></img><span className="link-font-sml">Find Friends</span>
                            </div>
                            <div className="link-wrapper my-1">
                                <img className="link-font-sml me-2 inline-block" src={Accountsetting}></img><span className="link-font-sml">Account Settings</span>
                            </div>
                            <NavDropdown.Divider />
                            <NavDropdown.Item className="p-0">
                                <div className="text-center options">
                                    {
                                        currentUser ?
                                            <a className="option" onClick={toggleUser}>
                                                Sign Out
                                            </a>
                                            :
                                            <a><Link className="option" to="/signin">Sign In</Link></a>
                                    }
                                </div>
                            </NavDropdown.Item>
                        </NavDropdown>
                    </div>
                </Navbar.Collapse>
            </Navbar>
        </div>
    )
}

export default Navmenu

如果我不得不猜测为什么用户不保持“注销”状态,那是因为 useEffect 中的内部订阅也没有取消订阅。它仍然可以更新 user 状态。

确保当 onAuthStateChanged 回调中存在错误的 userAuth 值时,您也将 currentUser 状态重置为 null。

useEffect(() => {
  let unsubscribeFromAuth = null;
  let unsubscribeFromSnapshot = null;

  unsubscribeFromAuth = auth.onAuthStateChanged(async userAuth => {
    if (userAuth) {
      const userRef = await createUserProfileDocument(userAuth);

      unsubscribeFromSnapshot = userRef.onSnapshot(snapShot => {
        setUser({
          id: snapShot.id,
          ...snapShot.data()
        });
      });
    } else {
      setUser(null); // <-- reset user state
    }
  });

  return () => {
    unsubscribeFromAuth();
    unsubscribeFromSnapshot();
  };
}, []);