React使用受保护路由时触发Cannot read property 'push' of undefined报错如何解决
问题根因
- Login组件参数接收错误
你当前的组件入参写法是const Login = ({ submitForm }, props) => {,函数组件的所有属性只会通过第一个参数传递,你这里将第二个参数写为props,实际拿到的props是undefined,自然无法读取history属性触发报错。 - 异步登录逻辑时序错误
fetchLogin是异步方法,你现在调用后立刻执行跳转,会出现接口还没返回登录结果就已经触发跳转的逻辑漏洞。 - 登录状态持久化逻辑缺失
你当前的Auth类的authenticated属性只存在于运行内存中,用户刷新页面后这个值会重置为false,导致已经登录的用户还要重新登录。
修复代码示例
修复后的Login组件
// 修正参数接收逻辑 const Login = (props) => { const [isSubmitted, setIsSubmitted] = useState(false); function submitForm() { setIsSubmitted(true); } const { handleChange, values, handleSubmit, errors } = useForm( submitForm, validate ); const [loading, setLoading] = useState(false); const [error, setError] = useState(false); const [login, setLogin] = useState(false); const fetchLogin = async (email, password) => { try { setLoading(true); const res = await Axios({ //API CALL }); if (res.status === 200) { setLogin(true); localStorage.setItem("user-info", JSON.stringify(res)); // 登录成功后再更新登录状态、触发跳转 auth.login(() => { props.history.push("/admin"); }); } setLoading(false); } catch (err) { setError(err.message); setLoading(false); } }; function loginButton() { fetchLogin(values.email, values.password); } return ( <form> </form> ); }; // 如果Route没有自动注入history属性,用withRouter包裹组件即可 // import { withRouter } from 'react-router-dom' // export default withRouter(Login); export default Login;
优化后的Auth类
class Auth { constructor() { // 初始化时从本地存储读取登录状态 this.authenticated = !!localStorage.getItem("user-info"); } login(cb) { this.authenticated = true; cb(); } logout(cb) { this.authenticated = false; // 登出时清除本地存储的用户信息 localStorage.removeItem("user-info"); cb(); } isAuthenticated() { // 校验时同步判断本地存储状态,避免刷新后状态丢失 return this.authenticated && !!localStorage.getItem("user-info"); } } export default new Auth();
额外优化建议
- 如果使用react-router v5及以上版本,可直接在组件内使用
useHistoryhook获取history实例,不需要依赖props传递:
import { useHistory } from 'react-router-dom'; const Login = () => { const history = useHistory(); // 后续直接调用history.push("/admin")即可 }
- 404路由可配置为
path="*",语义更明确 - AdminLayout的传参可以直接写
component={AdminLayout},不需要额外包一层箭头函数,Route会自动把路由属性传递给组件
内容的提问来源于stack exchange,提问作者user6517334
相关产品推荐
相关产品推荐

