fix(NodeSwitcher): move all hooks before conditional return to comply with React hooks rules
Build and Deploy / Build and Push Docker Image (push) Successful in 2m1s

- All hooks (useState, useCallback, useEffect) must be called before any conditional return
- Add document check in updateRoleState
- Merge SSR and isMaster checks into single return at the end
This commit is contained in:
2026-08-08 04:15:55 +08:00
parent 36d39e4a47
commit 8ef0ff8507
+32 -33
View File
@@ -20,15 +20,6 @@ export function NodeSwitcher() {
const [mounted, setMounted] = useState(false)
const buttonRef = useRef<HTMLButtonElement>(null)
useEffect(() => {
setMounted(true)
// 在客户端初始化时从 localStorage 读取选中的节点
setSelected(activeInterconnectNodeId || 'local')
}, [])
// SSR 时返回 null,避免 hydration 错误
if (!mounted) return null
const fetchNodes = useCallback(async () => {
try {
const res = await getNodes()
@@ -48,15 +39,23 @@ export function NodeSwitcher() {
} else {
setNodes([])
setSelected('local')
if (typeof document !== 'undefined') {
const traveling = !!document.cookie.match(/(?:^| )active_interconnect_node_id=([^;]*)/)
if (activeInterconnectNodeId && !traveling) {
setActiveInterconnectNodeId('')
}
}
}
},
[fetchNodes],
)
useEffect(() => {
setMounted(true)
// 在客户端初始化时从 localStorage 读取选中的节点
setSelected(activeInterconnectNodeId || 'local')
}, [])
useEffect(() => {
;(async () => {
try {
@@ -85,27 +84,8 @@ export function NodeSwitcher() {
}
}, [isMaster, fetchNodes])
if (!isMaster) return null
const handleSelect = (val: string) => {
setSelected(val)
setOpen(false)
if (val === 'local') {
setActiveInterconnectNodeId('')
} else {
const name = nodes.find((n) => n.id === val)?.name || ''
setActiveInterconnectNodeId(val, name)
}
window.location.reload()
}
const allOptions = [
{ value: 'local', label: '本机节点' },
...nodes.map((n) => ({ value: n.id, label: n.name })),
]
const currentLabel = allOptions.find((o) => o.value === selected)?.label || '本机节点'
useEffect(() => {
if (!open) return
const updateMenuPosition = () => {
const el = buttonRef.current
if (!el) return
@@ -123,9 +103,6 @@ export function NodeSwitcher() {
: { top: rect.bottom + 6, bottom: 'auto' }),
})
}
useEffect(() => {
if (!open) return
updateMenuPosition()
const onDoc = (e: MouseEvent) => {
const t = e.target as Node
@@ -148,6 +125,28 @@ export function NodeSwitcher() {
}
}, [open])
// SSR 时或非 master 节点时返回 null
if (!mounted || !isMaster) return null
const handleSelect = (val: string) => {
setSelected(val)
setOpen(false)
if (val === 'local') {
setActiveInterconnectNodeId('')
} else {
const name = nodes.find((n) => n.id === val)?.name || ''
setActiveInterconnectNodeId(val, name)
}
window.location.reload()
}
const allOptions = [
{ value: 'local', label: '本机节点' },
...nodes.map((n) => ({ value: n.id, label: n.name })),
]
const currentLabel = allOptions.find((o) => o.value === selected)?.label || '本机节点'
return (
<div className="relative">
<button