Skip to content

Commit 7508757

Browse files
JonasBacodex
andauthored
fix(slot): Render nothing when no outlet is registered (#112568)
Render slot consumer content only after an outlet element is available. Previously the slot consumer rendered its children in place until the outlet mounted, then switched over to a portal. That caused a flash of content at the wrong DOM position and changed the rendered element type under React, which reset component identity across outlet lifecycle changes. This makes the consumer return `null` until the outlet is ready and updates the slot tests to match that behavior. Refs GH-112564 Co-authored-by: OpenAI Codex <noreply@openai.com>
1 parent c56f2d2 commit 7508757

File tree

2 files changed

+9
-9
lines changed

2 files changed

+9
-9
lines changed

static/app/components/core/slot/slot.spec.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ describe('slot', () => {
1111
expect(SlotModule.Fallback).toBeDefined();
1212
});
1313

14-
it('renders children in place when no Outlet is registered', () => {
14+
it('renders nothing when no Outlet is registered', () => {
1515
const SlotModule = slot(['header'] as const);
1616

1717
render(
@@ -22,7 +22,7 @@ describe('slot', () => {
2222
</SlotModule.Provider>
2323
);
2424

25-
expect(screen.getByText('inline content')).toBeInTheDocument();
25+
expect(screen.queryByText('inline content')).not.toBeInTheDocument();
2626
});
2727

2828
it('portals children to the Outlet element', () => {
@@ -44,7 +44,7 @@ describe('slot', () => {
4444
);
4545
});
4646

47-
it('multiple slot consumers render their children independently', () => {
47+
it('multiple slot consumers render nothing independently when no Outlet is registered', () => {
4848
const SlotModule = slot(['a', 'b'] as const);
4949

5050
render(
@@ -58,8 +58,8 @@ describe('slot', () => {
5858
</SlotModule.Provider>
5959
);
6060

61-
expect(screen.getByText('slot a content')).toBeInTheDocument();
62-
expect(screen.getByText('slot b content')).toBeInTheDocument();
61+
expect(screen.queryByText('slot a content')).not.toBeInTheDocument();
62+
expect(screen.queryByText('slot b content')).not.toBeInTheDocument();
6363
});
6464

6565
it('consumer throws when rendered outside provider', () => {

static/app/components/core/slot/slot.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -140,8 +140,6 @@ function makeSlotConsumer<T extends Slot>(
140140
return () => dispatch({type: 'decrement counter', name});
141141
}, [dispatch, name]);
142142

143-
const element = state[name]?.element;
144-
145143
// Provide outletNameContext from the consumer so that portaled children
146144
// (which don't descend through the outlet in the component tree) can still
147145
// read which slot they belong to via useSlotOutletRef.
@@ -151,10 +149,12 @@ function makeSlotConsumer<T extends Slot>(
151149
</outletNameContext.Provider>
152150
);
153151

152+
const element = state[name]?.element;
153+
154154
if (!element) {
155-
// Render in place as a fallback when no target element is registered yet
156-
return wrappedChildren;
155+
return null;
157156
}
157+
158158
return createPortal(wrappedChildren, element);
159159
}
160160

0 commit comments

Comments
 (0)