带有 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();
};
}, []);
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();
};
}, []);