fix: use createPortal for DetailPanel instead of context+useEffect
The previous approach used useEffect+context to hoist DetailPanel content to the AppShell level, but the dependency-free useEffect caused a re-render loop that broke sidebar navigation. Replace with createPortal: pages render DetailPanel inline in their JSX but portal it to a target div (#detail-panel-portal) at the AppShell level. No state lifting, no re-render loops. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { useState, useMemo, useEffect } from 'react';
|
||||
import { useState, useMemo } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { useParams, Link } from 'react-router';
|
||||
import {
|
||||
StatCard, StatusDot, Badge, MonoText, ProgressBar,
|
||||
@@ -9,7 +10,6 @@ import styles from './AgentHealth.module.css';
|
||||
import { useAgents, useAgentEvents } from '../../api/queries/agents';
|
||||
import { useAgentMetrics } from '../../api/queries/agent-metrics';
|
||||
import type { AgentInstance } from '../../api/types';
|
||||
import { useDetailPanelSlot } from '../../components/DetailPanelContext';
|
||||
|
||||
// ── Helpers ──────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -219,7 +219,6 @@ export default function AgentHealth() {
|
||||
const { appId } = useParams();
|
||||
const { data: agents } = useAgents(undefined, appId);
|
||||
const { data: events } = useAgentEvents(appId);
|
||||
const { setDetail } = useDetailPanelSlot();
|
||||
|
||||
const [selectedInstance, setSelectedInstance] = useState<AgentInstance | null>(null);
|
||||
const [panelOpen, setPanelOpen] = useState(false);
|
||||
@@ -505,41 +504,18 @@ export default function AgentHealth() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Detail panel — hoisted to AppShell via context */}
|
||||
<AgentDetailPanelSlot
|
||||
open={panelOpen}
|
||||
onClose={() => { setPanelOpen(false); setSelectedInstance(null); }}
|
||||
selectedInstance={selectedInstance}
|
||||
detailTabs={detailTabs}
|
||||
setDetail={setDetail}
|
||||
/>
|
||||
{/* Detail panel — portaled to AppShell level for proper slide-in */}
|
||||
{selectedInstance && document.getElementById('detail-panel-portal') &&
|
||||
createPortal(
|
||||
<DetailPanel
|
||||
open={panelOpen}
|
||||
onClose={() => { setPanelOpen(false); setSelectedInstance(null); }}
|
||||
title={selectedInstance.name ?? selectedInstance.id}
|
||||
tabs={detailTabs}
|
||||
/>,
|
||||
document.getElementById('detail-panel-portal')!,
|
||||
)
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function AgentDetailPanelSlot({
|
||||
open, onClose, selectedInstance, detailTabs, setDetail,
|
||||
}: {
|
||||
open: boolean
|
||||
onClose: () => void
|
||||
selectedInstance: AgentInstance | null
|
||||
detailTabs: any
|
||||
setDetail: (node: React.ReactNode) => void
|
||||
}) {
|
||||
useEffect(() => {
|
||||
if (!selectedInstance) {
|
||||
setDetail(null);
|
||||
return;
|
||||
}
|
||||
setDetail(
|
||||
<DetailPanel
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
title={selectedInstance.name ?? selectedInstance.id}
|
||||
tabs={detailTabs}
|
||||
/>
|
||||
);
|
||||
return () => setDetail(null);
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user