React登录页出现TypeError: Cannot read property 'token' of undefined问题求助
解决React Auth Context中的TypeError: Cannot read property 'token' of undefined错误
嘿,我来帮你一步步解决这个问题!咱们先定位错误根源,再逐个修复,顺便给你补点token和授权的基础知识点。
错误直接原因
你在Login.js里尝试读取response.token和response.data.token,这里有两个核心问题:
- Fetch返回的
response对象本身没有token或data属性——你需要先把响应解析成JSON格式,而不是纯文本。 - 你用了
response.text()读取响应,但后端返回的应该是包含token的JSON数据,不是纯字符串"redirect"。
步骤1:修复Login组件的API请求逻辑
先修改Login.js里的请求处理,同时解决函数组件不能用this.props.history的问题:
import React, { useState, useContext, useHistory } from "react"; import MuiThemeProvider from "material-ui/styles/MuiThemeProvider"; import AppBar from "material-ui/AppBar"; import RaisedButton from "material-ui/RaisedButton"; import TextField from "material-ui/TextField"; import { Link } from "react-router-dom"; import { AuthContext } from "./auth"; import "./App.css"; const Login = () => { const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const { setToken } = useContext(AuthContext); const history = useHistory(); // 用useHistory hook替代this.props.history const performLogin = async e => { e.preventDefault(); const body = { password, email }; console.log(body); const options = { method: "POST", headers: { "Content-Type": "application/json", Accept: "application/json" }, body: JSON.stringify(body) }; try { const response = await fetch("/api/authenticate", options); // 把response.text()改成response.json(),解析后端返回的JSON数据 const data = await response.json(); // 请和后端确认登录成功的返回格式,这里假设后端返回{ success: true, token: "xxx" } if (data.success) { setToken(data.token); // 将后端返回的token存入Context history.push("/dashboard"); // 登录成功后直接跳转 } else if (data.message === "verifyemail") { history.push(`/verifyOtp/${email}`); // 直接用当前email变量 } else { console.log("login failed"); window.alert("login failed"); } } catch (error) { console.error(error); } }; return ( <> <div className="loginForm"> <MuiThemeProvider> <TextField hintText="Enter your Email" floatingLabelText="Email" onChange={(event, newValue) => setEmail(newValue)} /> <br /> <TextField type="password" hintText="Enter your password" floatingLabelText="password" onChange={(event, newValue) => setPassword(newValue)} /> <br /> <RaisedButton label="Submit" primary={true} style={{ margin: 15 }} onClick={performLogin} /> </MuiThemeProvider> </div> </> ); }; export default Login;
步骤2:修复AuthContext的Reducer逻辑
你的auth.js里的reducer写法不符合useReducer规范(应为(state, action)格式),同时优化localStorage同步逻辑:
import React, { createContext, useReducer, useEffect } from "react"; // 重构reducer,用action类型区分操作 const reducer = (state, action) => { switch (action.type) { case "SET_TOKEN": if (!action.payload) { localStorage.removeItem("token"); return null; } return action.payload; case "CLEAR_TOKEN": localStorage.removeItem("token"); return null; default: return state; } }; // 从localStorage初始化token const initialState = localStorage.getItem("token") ? JSON.parse(localStorage.getItem("token")) : null; const AuthContext = createContext(); function AuthProvider(props) { const [token, dispatch] = useReducer(reducer, initialState); // token变化时同步到localStorage useEffect(() => { if (token) { localStorage.setItem("token", JSON.stringify(token)); } else { localStorage.removeItem("token"); } }, [token]); // 给Context提供统一操作方法 const setToken = (newToken) => { dispatch({ type: "SET_TOKEN", payload: newToken }); }; const logOut = () => { dispatch({ type: "CLEAR_TOKEN" }); }; return ( <AuthContext.Provider value={{ token, setToken, logOut }}> {props.children} </AuthContext.Provider> ); } export { AuthContext, AuthProvider };
步骤3:修复App.js的路由顺序
原Switch里Redirect在PrivateRoute前面,会导致Dashboard永远无法匹配,调整顺序:
import React from "react"; import { BrowserRouter as Router, Redirect, Switch, Route, Link } from "react-router-dom"; import "./App.css"; import Login from "./login"; import Dashboard from "./dashboard"; import { AuthProvider } from "./auth"; import PrivateRoute from "./PrivateRoute"; function App(props) { return ( <> <AuthProvider> <Router> <div className="container"> <nav className="navbar navbar-expand-lg navheader"> <div className="collapse navbar-collapse"> <ul className="navbar-nav mr-auto"> <li className="nav-item"> <Link to={"/Login"} className="nav-link"> Login </Link> </li> </ul> </div> </nav> <br /> <Switch> <Route exact path="/login" component={Login} /> {/* PrivateRoute放在Redirect前面 */} <PrivateRoute exact path="/dashboard" component={Dashboard} /> {/* 默认路由重定向到登录页 */} <Route render={() => <Redirect to="/login" />} /> </Switch> </div> </Router> </AuthProvider> </> ); } export default App;
步骤4:优化Dashboard组件
直接使用AuthContext提供的logOut方法,简化代码:
import React, { useContext } from "react"; import { AuthContext } from "./auth"; const Dashboard = () => { const { logOut } = useContext(AuthContext); return ( <> <div>hello</div> <button onClick={logOut}>Logout</button> </> ); }; export default Dashboard;
关于Token和授权的基础科普
- Token是什么?:它是后端生成的一串加密字符串,用来证明你已成功登录。前端拿到后需存在localStorage/Context中,后续请求后端接口时,要把Token放在请求头(比如
Authorization: Bearer ${token}),后端通过验证Token判断你是否有权限访问受保护资源。 - 这次错误是前端还是后端问题?:主要是前端处理响应的方式不对,但你需要和后端确认:
- 登录接口返回的JSON格式(是否包含token字段)
- 登录成功/失败/需验证邮箱时的返回标识
内容的提问来源于stack exchange,提问作者UbuntuNewb
相关产品推荐
相关产品推荐

