Skip to content

fix: move TopBar content update out of render phase#493

Open
TheDarkSkyXD wants to merge 1 commit intospacedriveapp:mainfrom
TheDarkSkyXD:fix/topbar-setstate-during-render
Open

fix: move TopBar content update out of render phase#493
TheDarkSkyXD wants to merge 1 commit intospacedriveapp:mainfrom
TheDarkSkyXD:fix/topbar-setstate-during-render

Conversation

@TheDarkSkyXD
Copy link
Contributor

Summary

  • useSetTopBar was calling store.setContent() synchronously during render, causing React's "Cannot update a component while rendering a different component" warning (TopBar updated while Overview renders)
  • Wrapped the content update in useEffect so it runs after render, and added cleanup to reset content on unmount

Test plan

  • Verify the "Cannot update a component (TopBar) while rendering a different component (Overview)" console warning is gone
  • Verify TopBar content still renders correctly on the Overview page and other routes using useSetTopBar

🤖 Generated with Claude Code

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 25, 2026

Walkthrough

The useSetTopBar hook in TopBar.tsx has been refactored to defer content updates from render-time to post-render lifecycle. It now uses a useRef to store the latest node and a useEffect to handle the store update with cleanup logic.

Changes

Cohort / File(s) Summary
Hook Lifecycle Refactoring
interface/src/components/TopBar.tsx
Modified useSetTopBar to move store.setContent(node) calls from render phase to post-render via useEffect. Introduced useRef for deferred node storage and effect cleanup that resets store with store.setContent(null) on unmount or re-run.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: moving TopBar content update from render phase to post-render (useEffect), which is the core issue addressed in this PR.
Description check ✅ Passed The description is directly related to the changeset, explaining the problem (synchronous store.setContent() during render causing React warnings) and the solution (wrapping in useEffect with cleanup).
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

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>
@TheDarkSkyXD TheDarkSkyXD force-pushed the fix/topbar-setstate-during-render branch from 25b8288 to 91da96d Compare March 25, 2026 23:41
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between b7d5dd2 and 91da96d.

📒 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";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n interface/src/components/TopBar.tsx | head -80

Repository: spacedriveapp/spacebot

Length of output: 2841


🏁 Script executed:

rg "useSetTopBar" -A 3 -B 1

Repository: spacedriveapp/spacebot

Length of output: 3773


🏁 Script executed:

sed -n '1,100p' interface/src/routes/Overview.tsx | tail -60

Repository: spacedriveapp/spacebot

Length of output: 2116


🏁 Script executed:

sed -n '40,100p' interface/src/routes/Overview.tsx

Repository: spacedriveapp/spacebot

Length of output: 2158


🏁 Script executed:

sed -n '1,50p' interface/src/routes/Overview.tsx

Repository: 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.md

Repository: 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant