From 68b02895567513d6b04775eb8dc2ee0e1bd54554 Mon Sep 17 00:00:00 2001 From: JonasBa Date: Thu, 9 Apr 2026 11:11:13 +0200 Subject: [PATCH] fix(slot): Render nothing when no outlet is registered Stop rendering slot consumer content in place when no outlet element is available. This avoids the initial flash at the wrong DOM position and prevents remounts caused by switching from inline rendering to a portal once the outlet appears. Co-Authored-By: OpenAI Codex --- static/app/components/core/slot/slot.spec.tsx | 10 +++++----- static/app/components/core/slot/slot.tsx | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/static/app/components/core/slot/slot.spec.tsx b/static/app/components/core/slot/slot.spec.tsx index d23b461d357c..16b2a26123fb 100644 --- a/static/app/components/core/slot/slot.spec.tsx +++ b/static/app/components/core/slot/slot.spec.tsx @@ -11,7 +11,7 @@ describe('slot', () => { expect(SlotModule.Fallback).toBeDefined(); }); - it('renders children in place when no Outlet is registered', () => { + it('renders nothing when no Outlet is registered', () => { const SlotModule = slot(['header'] as const); render( @@ -22,7 +22,7 @@ describe('slot', () => { ); - expect(screen.getByText('inline content')).toBeInTheDocument(); + expect(screen.queryByText('inline content')).not.toBeInTheDocument(); }); it('portals children to the Outlet element', () => { @@ -44,7 +44,7 @@ describe('slot', () => { ); }); - it('multiple slot consumers render their children independently', () => { + it('multiple slot consumers render nothing independently when no Outlet is registered', () => { const SlotModule = slot(['a', 'b'] as const); render( @@ -58,8 +58,8 @@ describe('slot', () => { ); - expect(screen.getByText('slot a content')).toBeInTheDocument(); - expect(screen.getByText('slot b content')).toBeInTheDocument(); + expect(screen.queryByText('slot a content')).not.toBeInTheDocument(); + expect(screen.queryByText('slot b content')).not.toBeInTheDocument(); }); it('consumer throws when rendered outside provider', () => { diff --git a/static/app/components/core/slot/slot.tsx b/static/app/components/core/slot/slot.tsx index a405d3476a0a..62467617aa47 100644 --- a/static/app/components/core/slot/slot.tsx +++ b/static/app/components/core/slot/slot.tsx @@ -140,8 +140,6 @@ function makeSlotConsumer( return () => dispatch({type: 'decrement counter', name}); }, [dispatch, name]); - const element = state[name]?.element; - // Provide outletNameContext from the consumer so that portaled children // (which don't descend through the outlet in the component tree) can still // read which slot they belong to via useSlotOutletRef. @@ -151,10 +149,12 @@ function makeSlotConsumer( ); + const element = state[name]?.element; + if (!element) { - // Render in place as a fallback when no target element is registered yet - return wrappedChildren; + return null; } + return createPortal(wrappedChildren, element); }