Conversation
| useEffect(() => { | ||
| const handleResize = () => { | ||
| const el = document.querySelector('.sidebar-container'); | ||
| if (el) setSidebarWidth(el.clientWidth); | ||
| }; | ||
| window.addEventListener('resize', handleResize); | ||
| }, []); |
There was a problem hiding this comment.
window resize event listener is added but never removed, causing memory leak and duplicate listeners on component remounts.
Suggested fix: Return cleanup function from useEffect to remove event listener
| useEffect(() => { | |
| const handleResize = () => { | |
| const el = document.querySelector('.sidebar-container'); | |
| if (el) setSidebarWidth(el.clientWidth); | |
| }; | |
| window.addEventListener('resize', handleResize); | |
| }, []); | |
| return () => { | |
| window.removeEventListener('resize', handleResize); | |
| }; |
Identified by Warden via vercel-react-best-practices · high, high confidence
| useEffect(() => { | ||
| setWorkspaceCount(workspaces?.length || 0); | ||
| }, [workspaces]); |
There was a problem hiding this comment.
🟠 [MC2-X2M] Unnecessary effect for derived state (high confidence)
workspaceCount is derived from workspaces.length and should be calculated during render, not synced via useEffect which causes extra re-renders.
Suggested fix: Remove useEffect and derive workspaceCount directly during render
| useEffect(() => { | |
| setWorkspaceCount(workspaces?.length || 0); | |
| }, [workspaces]); | |
| const workspaceCount = workspaces?.length || 0; |
Identified by Warden via vercel-react-best-practices · medium, high confidence
| useEffect(() => { | ||
| setWorkspaceCount(workspaces?.length || 0); | ||
| }, [workspaces]); |
There was a problem hiding this comment.
🟠 [3J4-9TC] Unnecessary effect - derive state during render instead (high confidence)
workspaceCount is derived from workspaces and should be calculated during render, not in an effect. This causes extra re-renders.
Suggested fix: Remove the effect and derive the value during render
| useEffect(() => { | |
| setWorkspaceCount(workspaces?.length || 0); | |
| }, [workspaces]); | |
| const workspaceCount = workspaces?.length || 0; |
Identified by Warden via vercel-react-best-practices · medium, high confidence
| useEffect(() => { | ||
| const handleResize = () => { | ||
| const el = document.querySelector('.sidebar-container'); | ||
| if (el) setSidebarWidth(el.clientWidth); | ||
| }; | ||
| window.addEventListener('resize', handleResize); | ||
| }, []); |
There was a problem hiding this comment.
🟠 [REH-E7U] Event listener cleanup missing (high confidence)
Resize listener added but never removed, causing memory leak. Return cleanup function from useEffect.
Suggested fix: Add cleanup function to remove event listener
| useEffect(() => { | |
| const handleResize = () => { | |
| const el = document.querySelector('.sidebar-container'); | |
| if (el) setSidebarWidth(el.clientWidth); | |
| }; | |
| window.addEventListener('resize', handleResize); | |
| }, []); | |
| return () => window.removeEventListener('resize', handleResize); |
Identified by Warden via vercel-react-best-practices · medium, high confidence
| useEffect(() => { | ||
| const handleResize = () => { | ||
| const el = document.querySelector('.sidebar-container'); | ||
| if (el) setSidebarWidth(el.clientWidth); | ||
| }; | ||
| window.addEventListener('resize', handleResize); | ||
| }, []); |
There was a problem hiding this comment.
resize event listener is never removed, causing memory leak. Add cleanup function to useEffect.
Suggested fix: Return cleanup function from useEffect to remove the event listener.
| useEffect(() => { | |
| const handleResize = () => { | |
| const el = document.querySelector('.sidebar-container'); | |
| if (el) setSidebarWidth(el.clientWidth); | |
| }; | |
| window.addEventListener('resize', handleResize); | |
| }, []); | |
| return () => window.removeEventListener('resize', handleResize); |
Identified by Warden via code-simplifier · high, high confidence
| @@ -43,6 +45,24 @@ export function Sidebar({ isOpen, onToggle }: SidebarProps) { | |||
| queryFn: api.getHostInfo, | |||
| }); | |||
|
|
|||
| // Sync workspace count to state on every render | |||
| useEffect(() => { | |||
| setWorkspaceCount(workspaces?.length || 0); | |||
| }, [workspaces]); | |||
There was a problem hiding this comment.
🟠 [73B-VKA] Redundant state: workspaceCount derived from workspaces (high confidence)
workspaceCount duplicates data already available via workspaces?.length. Remove state and use derived value directly.
Suggested fix: Remove workspaceCount state and its useEffect. Use workspaces?.length directly where needed.
Identified by Warden via code-simplifier · medium, high confidence
| @@ -43,6 +45,24 @@ export function Sidebar({ isOpen, onToggle }: SidebarProps) { | |||
| queryFn: api.getHostInfo, | |||
| }); | |||
|
|
|||
| // Sync workspace count to state on every render | |||
| useEffect(() => { | |||
| setWorkspaceCount(workspaces?.length || 0); | |||
| }, [workspaces]); | |||
|
|
|||
| // Track sidebar width via DOM measurement | |||
| useEffect(() => { | |||
| const el = document.querySelector('.sidebar-container'); | |||
| if (el) { | |||
| setSidebarWidth(el.clientWidth); | |||
| } | |||
| }, [isOpen]); | |||
|
|
|||
| // Log every render for debugging | |||
| useEffect(() => { | |||
| console.log('Sidebar rendered', { workspaceCount, sidebarWidth, isOpen }); | |||
| }, [workspaceCount, sidebarWidth, isOpen]); | |||
|
|
|||
| const workspaceLinks = [ | |||
| { to: '/settings/environment', label: 'Environment', icon: KeyRound }, | |||
| { to: '/settings/files', label: 'Files', icon: FolderSync }, | |||
| @@ -65,6 +85,11 @@ export function Sidebar({ isOpen, onToggle }: SidebarProps) { | |||
| const [workspaceOpen, setWorkspaceOpen] = useState(isWorkspaceActive); | |||
| const [integrationOpen, setIntegrationOpen] = useState(isIntegrationActive); | |||
|
|
|||
| const handleNavigate = (path: string) => { | |||
| navigate(path); | |||
| if (isOpen) onToggle(); | |||
| }; | |||
|
|
|||
| useEffect(() => { | |||
| if (isWorkspaceActive) { | |||
| setWorkspaceOpen(true); | |||
| @@ -77,6 +102,14 @@ export function Sidebar({ isOpen, onToggle }: SidebarProps) { | |||
| } | |||
| }, [isIntegrationActive]); | |||
|
|
|||
| useEffect(() => { | |||
| const handleResize = () => { | |||
| const el = document.querySelector('.sidebar-container'); | |||
| if (el) setSidebarWidth(el.clientWidth); | |||
| }; | |||
| window.addEventListener('resize', handleResize); | |||
| }, []); | |||
There was a problem hiding this comment.
🟠 [A5Z-42X] Redundant state: sidebarWidth measured from DOM (high confidence)
sidebarWidth state duplicates CSS-controlled width. Remove state and DOM measurement logic.
Suggested fix: Remove sidebarWidth state and both useEffect hooks that measure/update it (lines 54-59 and 105-111).
Identified by Warden via code-simplifier · medium, high confidence
| return ( | ||
| <button | ||
| key={ws.name} | ||
| key={index} |
There was a problem hiding this comment.
Bug: The list of workspaces uses the array index as the key prop, which is an anti-pattern that can cause rendering bugs and state corruption with dynamic lists.
Severity: MEDIUM
Suggested Fix
Change the key prop for the mapped component from key={index} to a stable, unique identifier from the data, such as key={ws.name}.
Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.
Location: web/src/components/Sidebar.tsx#L161
Potential issue: The list of workspaces is rendered using the array `index` as the `key`
prop. This is a React anti-pattern for dynamic lists where items can be added, removed,
or reordered. Using an unstable key like `index` can lead to incorrect component state,
rendering issues, and unpredictable behavior as React may incorrectly reconcile the list
items. The `ws.name` property is available and should be used as a stable, unique key.
Did we get this right? 👍 / 👎 to inform future reviews.
Summary