React多组件Navbar状态不更新:登录后仅Login组件显示变化
问题描述
我在多个组件中复用了Navbar组件,但当登录状态改变时,仅Login组件内的Navbar会显示更新后的状态(展示LOG OUT),其他组件(如首页Upcoming组件)的Navbar仍显示未登录状态(展示SIGN UP和LOGIN)。登录成功后页面跳转到首页,但首页的Navbar未同步更新状态。
相关组件代码
Navbar组件
import React, { useEffect } from "react"; import { Link } from "react-router-dom"; import logo from "../CINEPHILE1.png"; function Navbar({login}) { console.log(login) useEffect(() => { console.log(login) }) return ( <> <nav className="navbar "> <div className="container-fluid"> <Link to="/"> <img src={logo} alt="" width="100" height="50" /> </Link> <Link to="/cinephile/movies" className="text-decoration-none text-white " > MOVIES </Link> <Link to="/cinephile/tv-shows" className="text-decoration-none text-white " > TV SHOWS </Link> {login? ( <Link to ="/cinephile/logout" className="text-decoration-none text-white"> LOG OUT </Link> ) : ( <> <Link to="/cinephile/signup" className="text-decoration-none text-white " > SIGN UP </Link> <Link to="/cinephile/login" className="text-decoration-none text-white" > LOGIN </Link> </> )} </div> </nav> </> ); } export default Navbar;
Login组件
import React, { useState } from "react"; import axios from "axios"; import { useNavigate } from "react-router-dom"; import { toast } from "react-toastify"; import Navbar from "./Navbar"; function Login() { const [login,setLogin] =useState(false) const [email,setEmail] = useState("") const [password,setPassword] = useState("") const navigate = useNavigate() const handleSubmit = async(e) => { e.preventDefault() try { const sendData = await axios.post("http://localhost:5000/cinephile/account/login",{email,password}) if(sendData){ setLogin(true) navigate("/") } } catch (error) { toast("Invalid Credentials") } } return ( <> <Navbar login={login}/> <form onSubmit={handleSubmit}> <div className="mb-3"> Email address <input type="email" className="form-control" aria-describedby="emailHelp" onChange={(e)=>setEmail(e.target.value)} required></input> </div> <div class="mb-3"> Password <input type="password" className="form-control" onChange={(e)=>setPassword(e.target.value)} required></input> </div> <button type="submit" className="btn btn-primary" >LOGIN</button> </form> </> ); } export default Login;
Home(Upcoming)组件
import React, { useEffect, useState } from "react"; import { Link } from "react-router-dom"; import axios from "axios"; import Navbar from "./Navbar"; function Upcoming() { const [data,setData] = useState([]) const fetchData = async() => { try { const getData = await axios.get("https://api.themoviedb.org/3/movie/upcoming",{ params: {language: 'en-US', page: '1'}, headers: { accept: 'application/json', Authorization: `${process.env.REACT_APP_API_TOKEN}` } }) setData(getData.data.results) } catch (error) { } } useEffect(()=>{ fetchData() },[]) return ( <> <Navbar/> <main> <p className="fst-italic fs-2 text-start text-white">Upcoming</p> <div className="images_list"> {data.map((value,i)=>( <div className="images"> <Link to ={`/cinephile/${value.id}`}> <img src={`https://image.tmdb.org/t/p/w500/${value.poster_path}`} height="300px" alt="" key={i}/> </Link> <h4 className="text-white"> {value.original_title} </h4> </div> )) } </div> </main> </> ); } export default Upcoming;
Router组件
import React from "react"; import SignUp from "./Signup"; import Navbar from "./Navbar" import Login from "./Login"; import Upcoming from "./Upcoming"; import UpcomingDetails from "./UpcomingDetails"; import { Routes, Route } from "react-router-dom"; function Router() { return( <> <Routes> <Route exact path = "/" element = {<> <Navbar/> <Upcoming/> </>}/> <Route path = "/cinephile/signup" element={<> <Navbar/> <SignUp/> </>}/> <Route path = "/cinephile/login" element={<> <Login/> </>}/> <Route path = "/cinephile/:id" element={<> <Navbar/> <UpcomingDetails/> </>}/> </Routes> </> ) } export default Router;
问题原因
- 登录状态
login是Login组件内部的局部state,只能作用于当前组件内的Navbar,无法在其他组件间共享。调用setLogin(true)只会更新Login组件内的Navbar,其他组件的Navbar根本获取不到这个状态值。 - 首页Upcoming组件和Router中渲染的Navbar都没有传入
loginprops,默认值为undefined,因此始终显示未登录状态。 - 页面跳转后,首页组件重新渲染,但没有任何机制获取登录状态,自然无法同步更新Navbar。
解决方案
推荐两种常用的解决方式,可根据项目规模选择:
方式一:React Context 全局状态共享(适合中大型项目)
1. 创建AuthContext
新建AuthContext.js文件:
import React, { createContext, useState, useContext } from 'react'; const AuthContext = createContext(); export function AuthProvider({ children }) { // 从localStorage读取初始状态,防止页面刷新丢失登录状态 const [isLoggedIn, setIsLoggedIn] = useState(() => { return localStorage.getItem('isLoggedIn') === 'true'; }); // 全局登录方法 const login = () => { setIsLoggedIn(true); localStorage.setItem('isLoggedIn', 'true'); }; // 全局登出方法 const logout = () => { setIsLoggedIn(false); localStorage.removeItem('isLoggedIn'); }; return ( <AuthContext.Provider value={{ isLoggedIn, login, logout }}> {children} </AuthContext.Provider> ); } // 自定义Hook,简化组件调用 export function useAuth() { return useContext(AuthContext); }
2. 全局包裹AuthProvider
在项目入口文件(如index.js)中,用AuthProvider包裹整个应用:
import React from 'react'; import ReactDOM from 'react-dom/client'; import { BrowserRouter } from 'react-router-dom'; import { AuthProvider } from './AuthContext'; import Router from './Router'; const root = ReactDOM.createRoot(document.getElementById('root')); root.render( <BrowserRouter> <AuthProvider> <Router /> </AuthProvider> </BrowserRouter> );
3. 修改Navbar组件
通过自定义Hook获取全局登录状态:
import React from "react"; import { Link } from "react-router-dom"; import logo from "../CINEPHILE1.png"; import { useAuth } from "./AuthContext"; function Navbar() { const { isLoggedIn } = useAuth(); return ( <> <nav className="navbar "> <div className="container-fluid"> <Link to="/"> <img src={logo} alt="" width="100" height="50" /> </Link> <Link to="/cinephile/movies" className="text-decoration-none text-white " > MOVIES </Link> <Link to="/cinephile/tv-shows" className="text-decoration-none text-white " > TV SHOWS </Link> {isLoggedIn ? ( <Link to="/cinephile/logout" className="text-decoration-none text-white"> LOG OUT </Link> ) : ( <> <Link to="/cinephile/signup" className="text-decoration-none text-white " > SIGN UP </Link> <Link to="/cinephile/login" className="text-decoration-none text-white" > LOGIN </Link> </> )} </div> </nav> </> ); } export default Navbar;
4. 修改Login组件
调用全局登录方法:
import React, { useState } from "react"; import axios from "axios"; import { useNavigate } from "react-router-dom"; import { toast } from "react-toastify"; import Navbar from "./Navbar"; import { useAuth } from "./AuthContext"; function Login() { const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const navigate = useNavigate(); const { login } = useAuth(); const handleSubmit = async (e) => { e.preventDefault(); try { const sendData = await axios.post("http://localhost:5000/cinephile/account/login", { email, password }); if (sendData) { login(); navigate("/"); } } catch (error) { toast("Invalid Credentials"); } }; return ( <> <Navbar /> <form onSubmit={handleSubmit}> <div className="mb-3"> Email address <input type="email" className="form-control" aria-describedby="emailHelp" onChange={(e) => setEmail(e.target.value)} required></input> </div> <div className="mb-3"> Password <input type="password" className="form-control" onChange={(e) => setPassword(e.target.value)} required></input> </div> <button type="submit" className="btn btn-primary">LOGIN</button> </form> </> ); } export default Login;
5. 其他组件调整
所有使用Navbar的组件无需再传递login props,Navbar会自动从Context获取最新状态。
方式二:状态提升到顶层Router组件(适合小型项目)
1. 修改Router组件
将登录状态提升到Router,并提供状态更新方法:
import React, { useState } from "react"; import SignUp from "./Signup"; import Navbar from "./Navbar" import Login from "./Login"; import Upcoming from "./Upcoming"; import UpcomingDetails from "./UpcomingDetails"; import { Routes, Route } from "react-router-dom"; function Router() { const [isLoggedIn, setIsLoggedIn] = useState(() => { return localStorage.getItem('isLoggedIn') === 'true'; }); const handleLogin = () => { setIsLoggedIn(true); localStorage.setItem('isLoggedIn', 'true'); }; const handleLogout = () => { setIsLoggedIn(false); localStorage.removeItem('isLoggedIn'); }; return( <> <Routes> <Route exact path = "/" element = {<> <Navbar login={isLoggedIn}/> <Upcoming/> </>}/> <Route path = "/cinephile/signup" element={<> <Navbar login={isLoggedIn}/> <SignUp/> </>}/> <Route path = "/cinephile/login" element={<> <Login onLogin={handleLogin}/> </>}/> <Route path = "/cinephile/logout" element={<> <Navbar login={isLoggedIn}/> {/* 实现Logout组件并调用handleLogout */} </>}/> <Route path = "/cinephile/:id" element={<> <Navbar login={isLoggedIn}/> <UpcomingDetails/> </>}/> </Routes> </> ) } export default Router;
2. 修改Login组件
接收并调用onLogin方法:
function Login({ onLogin }) { // ... 原有代码 const handleSubmit = async (e) => { e.preventDefault(); try { const sendData = await axios.post("http://localhost:5000/cinephile/account/login", { email, password }); if (sendData) { onLogin(); navigate("/"); } } catch (error) { toast("Invalid Credentials"); } }; // ... 原有代码 }
内容的提问来源于stack exchange,提问作者Young Rishiraj
相关产品推荐
相关产品推荐

