fix: move TopBar content update out of render phase#493
fix: move TopBar content update out of render phase#493TheDarkSkyXD wants to merge 1 commit intospacedriveapp:mainfrom
Conversation
WalkthroughThe Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes 🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
useSetTopBar was calling store.setContent() synchronously during render, which triggered a setState on TopBar while Overview was still rendering. Wrap in useEffect so the update happens after render completes. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
25b8288 to
91da96d
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@interface/src/components/TopBar.tsx`:
- Line 1: The topbar write logic currently uses a no-deps useEffect causing
cleanup to run every render (store.setContent(null)) and producing flicker;
change the effect that writes content to useLayoutEffect with dependencies
[store, node] so it runs before paint and only when those change, and move the
cleanup to a separate effect with dependency [store] so store.setContent(null)
runs only on unmount. Locate the TopBar/useSetTopBar code paths and replace the
single no-deps useEffect that references store.setContent and node with two
effects: a useLayoutEffect([store, node]) to set content and a cleanup-only
useEffect([store]) that clears content on unmount.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 359cf87d-5b13-4142-b5f5-2c476ca7e4c0
📒 Files selected for processing (1)
interface/src/components/TopBar.tsx
| @@ -1,4 +1,4 @@ | |||
| import { createContext, useContext, useRef, useCallback, useSyncExternalStore, type ReactNode, type MouseEvent } from "react"; | |||
| import { createContext, useContext, useRef, useEffect, useCallback, useSyncExternalStore, type ReactNode, type MouseEvent } from "react"; | |||
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n interface/src/components/TopBar.tsx | head -80Repository: spacedriveapp/spacebot
Length of output: 2841
🏁 Script executed:
rg "useSetTopBar" -A 3 -B 1Repository: spacedriveapp/spacebot
Length of output: 3773
🏁 Script executed:
sed -n '1,100p' interface/src/routes/Overview.tsx | tail -60Repository: spacedriveapp/spacebot
Length of output: 2116
🏁 Script executed:
sed -n '40,100p' interface/src/routes/Overview.tsxRepository: spacedriveapp/spacebot
Length of output: 2158
🏁 Script executed:
sed -n '1,50p' interface/src/routes/Overview.tsxRepository: spacedriveapp/spacebot
Length of output: 1602
🏁 Script executed:
cat -n interface/src/components/TopBar.tsx | sed -n '8,35p'Repository: spacedriveapp/spacebot
Length of output: 831
🏁 Script executed:
cat > /tmp/verify_hooks.md << 'EOF'
# React Hook Behavior Verification
## Current Code (useEffect, no deps)
```javascript
useEffect(() => {
store.setContent(nodeRef.current);
return () => store.setContent(null);
});
```
- Effect runs after EVERY render
- Cleanup runs before next effect AND on unmount
- Result: store.setContent(null) executes on every rerender
## Proposed Code (useLayoutEffect, with deps)
```javascript
useLayoutEffect(() => {
store.setContent(node);
}, [store, node]);
useLayoutEffect(() => {
return () => store.setContent(null);
}, [store]);
```
- First effect: runs when store or node changes, BEFORE paint
- Second effect: cleanup runs when store changes or on unmount
- Since store is stable, cleanup only runs on unmount
- Result: store.setContent(null) only on unmount, not on every rerender
EOF
cat /tmp/verify_hooks.mdRepository: spacedriveapp/spacebot
Length of output: 860
Move topbar writes to layout effects with explicit dependencies to eliminate flickering and excessive store updates.
The no-deps useEffect runs after every render, including its cleanup that calls store.setContent(null) before each subsequent setup. With polling queries like Overview's 5–10 second refetch intervals and inline JSX passed to useSetTopBar, this causes repeated null → content cycles and stale content paints (since passive effects run after paint). Use useLayoutEffect with [store, node] for the content write (runs before paint) and a separate cleanup effect with [store] dependency (cleanup only on unmount), eliminating the per-rerender null resets and render-phase lag.
🛠️ Suggested fix
-import { createContext, useContext, useRef, useEffect, useCallback, useSyncExternalStore, type ReactNode, type MouseEvent } from "react";
+import { createContext, useContext, useRef, useLayoutEffect, useCallback, useSyncExternalStore, type ReactNode, type MouseEvent } from "react";
@@
export function useSetTopBar(node: ReactNode) {
const store = useContext(TopBarContext);
if (!store) throw new Error("useSetTopBar must be used within TopBarProvider");
- const nodeRef = useRef(node);
- nodeRef.current = node;
-
- useEffect(() => {
- store.setContent(nodeRef.current);
- return () => store.setContent(null);
- });
+ useLayoutEffect(() => {
+ store.setContent(node);
+ }, [store, node]);
+
+ useLayoutEffect(() => {
+ return () => store.setContent(null);
+ }, [store]);
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@interface/src/components/TopBar.tsx` at line 1, The topbar write logic
currently uses a no-deps useEffect causing cleanup to run every render
(store.setContent(null)) and producing flicker; change the effect that writes
content to useLayoutEffect with dependencies [store, node] so it runs before
paint and only when those change, and move the cleanup to a separate effect with
dependency [store] so store.setContent(null) runs only on unmount. Locate the
TopBar/useSetTopBar code paths and replace the single no-deps useEffect that
references store.setContent and node with two effects: a useLayoutEffect([store,
node]) to set content and a cleanup-only useEffect([store]) that clears content
on unmount.
Summary
useSetTopBarwas callingstore.setContent()synchronously during render, causing React's "Cannot update a component while rendering a different component" warning (TopBarupdated whileOverviewrenders)useEffectso it runs after render, and added cleanup to reset content on unmountTest plan
TopBar) while rendering a different component (Overview)" console warning is goneuseSetTopBar🤖 Generated with Claude Code