React升级后未登录用户访问页面报错及路由跳转问题
解决React Scripts升级到5.0后Userinfo页面未登录跳转报错问题
问题核心
未登录时user为null,但useFetch会先执行并尝试读取user.contractType导致报错;直接把user判断放在useFetch前又违反React Hooks不能条件调用的规则。以下是两种合规的解决方法:
方案一:给useFetch添加条件触发开关
给自定义的useFetch钩子增加enabled参数,仅当参数为true时发起请求,同时用可选链避免URL拼接报错:
import { useContext, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; import useFetch from './useFetch'; import AuthContext from './AuthContext'; const UserInfoPage = () => { const user = useContext(AuthContext); const navigate = useNavigate(); // 未登录跳转逻辑:副作用放在useEffect中执行 useEffect(() => { if (!user) { navigate('/'); } }, [user, navigate]); // 仅当user存在时触发请求,用可选链避免URL拼接报错 const { data, loading } = useFetch(`/contracts/${user?.contractType || ''}`, { enabled: !!user }); // 未登录时返回空或加载状态,避免渲染后续内容 if (!user) { return null; } return ( <div className="user-info"> {loading ? <p>加载中...</p> : <p>合同类型:{data?.name}</p>} </div> ); };
同时修改useFetch实现,支持enabled参数:
import { useState, useEffect } from 'react'; function useFetch(url, options = {}) { const [data, setData] = useState(null); const [loading, setLoading] = useState(false); useEffect(() => { // 未启用或URL无效时,终止请求 if (!options.enabled || !url.trim()) return; const fetchContract = async () => { setLoading(true); try { const res = await fetch(url); const result = await res.json(); setData(result); } catch (err) { console.error('请求合同数据失败:', err); } finally { setLoading(false); } }; fetchContract(); }, [url, options.enabled]); return { data, loading }; } export default useFetch;
方案二:拆分组件分离权限判断与业务逻辑
把需要user数据的业务逻辑抽成子组件,父组件先做权限判断和跳转,仅当user存在时才渲染子组件:
import { useContext, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; import AuthContext from './AuthContext'; import UserInfoContent from './UserInfoContent'; const UserInfoPage = () => { const user = useContext(AuthContext); const navigate = useNavigate(); useEffect(() => { if (!user) { navigate('/'); } }, [user, navigate]); // 未登录时返回空,不渲染子组件 if (!user) { return null; } // 子组件可安全使用user数据,内部调用useFetch return <UserInfoContent user={user} />; };
子组件UserInfoContent实现:
import useFetch from './useFetch'; const UserInfoContent = ({ user }) => { // 此处user已确保存在,可直接读取contractType const { data, loading } = useFetch(`/contracts/${user.contractType}`); return ( <div className="user-info"> {loading ? <p>加载中...</p> : <p>合同类型:{data?.name}</p>} </div> ); }; export default UserInfoContent;
方案说明
两种方案均严格遵守React Hooks调用规则(所有Hooks在组件顶层调用,无条件包裹):
- 方案一适合不想拆分组件的场景,通过控制
useFetch触发时机避免报错; - 方案二更符合组件职责分离的最佳实践,权限判断与业务逻辑解耦,代码更清晰。
内容的提问来源于stack exchange,提问作者Mega Animenia
相关产品推荐
相关产品推荐

