Skip to content

Fix span auto-selecting on mobile#136

Merged
mikhin merged 2 commits intomainfrom
fix-use-mobile
Feb 21, 2026
Merged

Fix span auto-selecting on mobile#136
mikhin merged 2 commits intomainfrom
fix-use-mobile

Conversation

@gVguy
Copy link
Copy Markdown
Contributor

@gVguy gVguy commented Feb 12, 2026

Problem

In the previous implementation in this useEffect in TraceViewer:

  useEffect(() => {
    // ...
    console.log(isMobile) // 👈 here

    if (!isMobile && selectedTraceSpans.length > 0 && !selectedSpan) {
      setSelectedSpan(selectedTraceSpans[0]);
    }
  }, [selectedTraceSpans, isMobile, selectedSpan]);

isMobile would first be false and on the next render true.

Because this is how we define it in useIsMobile:

return mounted ? isMobile : false

Back in that effect in TraceViewer 👆 This resulted in setSelectedSpan being called even on mobile, on that first render when isMobile was briefly false.

Solution

Expose isMounted, along with isMobile.

Update the hook in TraceViewer with:

if (!isMounted || isMobile) return;

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Fixes an issue where the first span was being auto-selected on mobile due to useIsMobile() initially returning false until the component mounted, causing desktop-only initialization logic to run on the first render.

Changes:

  • Updated useIsMobile() to expose isMounted alongside isMobile so callers can avoid acting on the pre-mount value.
  • Updated TraceViewer and SimpleTraceViewer to bail out of the “auto-select first span” effect when !isMounted || isMobile.
  • Cleaned up TraceViewer React import usage (no longer relies on React.useRef for initialization gating).

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
packages/ui/src/components/shared.ts Changes useIsMobile() to return { isMounted, isMobile } and preserves pre-mount isMobile as false.
packages/ui/src/components/TraceViewer/TraceViewer.tsx Uses isMounted to prevent initial auto-selection from running before mobile detection is stable.
packages/saas/src/components/SimpleTraceViewer.tsx Applies the same isMounted/isMobile guard to prevent unintended default selection on mobile.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/ui/src/components/shared.ts Outdated
Comment on lines +139 to +163
@@ -157,5 +157,8 @@ export const useIsMobile = () => {
return () => mediaQuery.removeEventListener("change", handleChange);
}, []);

return mounted ? isMobile : false;
return {
isMounted,
isMobile: isMounted ? isMobile : false,
};
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

useIsMobile previously returned a boolean but now returns an object with { isMounted, isMobile }. Since this hook is re-exported from the UI package entrypoint, this is an API-breaking change and the name no longer matches the return type. Consider either keeping useIsMobile() returning a boolean and introducing a new hook (e.g. useIsMobileState() / useMobileInfo()) for the {isMounted, isMobile} shape, or renaming this hook to reflect the new contract and updating any public docs/changelog accordingly.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Legit concern. Updated to a standalone useIsMounted() hook

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 78 to +84
useEffect(() => {
if (!hasInitialized.current) {
hasInitialized.current = true;
}
if (!isMounted || isMobile) return;

if (!isMobile && selectedTraceSpans.length > 0 && !selectedSpan) {
if (selectedTraceSpans.length > 0 && !selectedSpan) {
setSelectedSpan(selectedTraceSpans[0]);
}
}, [selectedTraceSpans, isMobile, selectedSpan]);
}, [selectedTraceSpans, isMobile, isMounted, selectedSpan]);
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

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

Gating the auto-selection effect behind a stateful useIsMounted() means span auto-selection can’t happen until after the “mounted” rerender, which adds an extra render cycle on desktop (mount rerender, then selection rerender). If you want to keep the behavior but avoid the extra render, consider skipping only the first effect run via a useRef flag instead of adding a mounted state dependency.

Copilot uses AI. Check for mistakes.
Comment on lines 44 to +50
useEffect(() => {
if (!isMobile && spans.length > 0 && !selectedSpan) {
if (!isMounted || isMobile) return;

if (spans.length > 0 && !selectedSpan) {
setSelectedSpan(spans[0]);
}
}, [spans, selectedSpan, isMobile]);
}, [spans, selectedSpan, isMobile, isMounted]);
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

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

Using useIsMounted() state to gate this auto-select effect delays selectedSpan initialization until after the mounted rerender, introducing an extra render on desktop. If that matters, consider skipping only the first effect run via a useRef flag (or similar) instead of relying on a mounted state update.

Copilot uses AI. Check for mistakes.
@mikhin mikhin merged commit dd8b685 into main Feb 21, 2026
7 checks passed
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.

3 participants