fix(admin-web): 修复 React #310 — useState 必须在条件 return 前调用
根因:expandedKeys 的 useState 调用位于 "if (!isAuthenticated) return null" 之后,违反 Rules of Hooks。认证状态变化时 hooks 调用数量不一致, React 报 #310 "Cannot update a component while rendering a different component"。 修复:将 activeKey 计算和 expandedKeys useState 全部移至条件 return 之前, 确保每次渲染 hooks 调用顺序完全一致。 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
300d55ff14
commit
96dad278ea
|
|
@ -100,28 +100,14 @@ export const AdminLayout: React.FC<{ children: React.ReactNode }> = ({ children
|
||||||
const { isAuthenticated, isLoading, user, logout } = useAuth();
|
const { isAuthenticated, isLoading, user, logout } = useAuth();
|
||||||
const [collapsed, setCollapsed] = useState(false);
|
const [collapsed, setCollapsed] = useState(false);
|
||||||
|
|
||||||
// 未登录 → 跳转 /login
|
|
||||||
//
|
|
||||||
// ⚠️ 必须在 useEffect 中执行,不能直接在 render 函数体内调用 router.replace()。
|
|
||||||
// 原因:render 期间调用 router 会触发父组件的状态更新(React #310 错误):
|
|
||||||
// "Cannot update a component while rendering a different component"
|
|
||||||
//
|
|
||||||
// useEffect 在 commit 阶段(DOM 更新后)执行,此时可以安全地触发导航。
|
|
||||||
// 依赖数组包含 router 是为了满足 exhaustive-deps lint 规则,router 引用稳定不会重复触发。
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isLoading && !isAuthenticated) {
|
|
||||||
router.replace('/login');
|
|
||||||
}
|
|
||||||
}, [isLoading, isAuthenticated, router]);
|
|
||||||
|
|
||||||
// 加载中或未登录时显示空白
|
|
||||||
if (isLoading || !isAuthenticated) return null;
|
|
||||||
|
|
||||||
// Derive activeKey from current pathname
|
// Derive activeKey from current pathname
|
||||||
|
// ⚠️ 必须在所有 useState/useEffect 之前计算,因为 expandedKeys 的初始值依赖它
|
||||||
const activeKey = pathname.replace(/^\//, '') || 'dashboard';
|
const activeKey = pathname.replace(/^\//, '') || 'dashboard';
|
||||||
|
|
||||||
// Auto-expand parent nav items based on current path
|
// ⚠️ 所有 useState / useEffect 必须在任何条件 return 之前调用(Rules of Hooks)
|
||||||
const getInitialExpanded = () => {
|
// 如果放在 "if (!isAuthenticated) return null" 之后,每次认证状态变化时
|
||||||
|
// hooks 调用数量不一致,React 会抛出 #310 错误。
|
||||||
|
const [expandedKeys, setExpandedKeys] = useState<string[]>(() => {
|
||||||
const expanded: string[] = [];
|
const expanded: string[] = [];
|
||||||
navItems.forEach(item => {
|
navItems.forEach(item => {
|
||||||
if (item.children && item.children.some(c => activeKey.startsWith(c.key) || activeKey === item.key)) {
|
if (item.children && item.children.some(c => activeKey.startsWith(c.key) || activeKey === item.key)) {
|
||||||
|
|
@ -129,8 +115,17 @@ export const AdminLayout: React.FC<{ children: React.ReactNode }> = ({ children
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return expanded;
|
return expanded;
|
||||||
};
|
});
|
||||||
const [expandedKeys, setExpandedKeys] = useState<string[]>(getInitialExpanded);
|
|
||||||
|
// 未登录 → 跳转 /login(useEffect 在 commit 阶段执行,避免 render 期间触发状态更新)
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isLoading && !isAuthenticated) {
|
||||||
|
router.replace('/login');
|
||||||
|
}
|
||||||
|
}, [isLoading, isAuthenticated, router]);
|
||||||
|
|
||||||
|
// 加载中或未登录时显示空白(条件 return 必须在所有 hooks 之后)
|
||||||
|
if (isLoading || !isAuthenticated) return null;
|
||||||
|
|
||||||
const toggleExpand = (key: string) => {
|
const toggleExpand = (key: string) => {
|
||||||
setExpandedKeys(prev =>
|
setExpandedKeys(prev =>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue