From c360ef7e10bb5881b3687285c53a1d6cab230000 Mon Sep 17 00:00:00 2001 From: Bao Nguyen Date: Thu, 13 Aug 2026 21:25:12 +0700 Subject: [PATCH] fix(portal): replay queued portal operations in order `PortalHost` queues portal operations that arrive before its `PortalManager` ref is attached, which is every portal that mounts in the first commit. `componentDidMount` drained that queue with `pop()`, replaying the operations LIFO, so portals mounted in the same commit were stacked in reverse source order. Drain with `shift()` instead. --- src/components/Portal/PortalHost.tsx | 4 +- src/components/__tests__/Portal.test.tsx | 48 ++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/src/components/Portal/PortalHost.tsx b/src/components/Portal/PortalHost.tsx index d8193007b8..ecc20b8a72 100644 --- a/src/components/Portal/PortalHost.tsx +++ b/src/components/Portal/PortalHost.tsx @@ -51,7 +51,9 @@ export default class PortalHost extends React.Component { const queue = this.queue; while (queue.length && manager) { - const action = queue.pop(); + // Replay in the order the operations were recorded, otherwise portals + // that mounted in the same commit end up stacked in reverse. + const action = queue.shift(); if (action) { switch (action.type) { case 'mount': diff --git a/src/components/__tests__/Portal.test.tsx b/src/components/__tests__/Portal.test.tsx index 14f07c141b..aa6c3b94cc 100644 --- a/src/components/__tests__/Portal.test.tsx +++ b/src/components/__tests__/Portal.test.tsx @@ -3,6 +3,8 @@ import { Text } from 'react-native'; import { expect, it, jest } from '@jest/globals'; import { render, screen } from '../../test-utils'; +import Dialog from '../Dialog/Dialog'; +import Modal from '../Modal'; import Portal from '../Portal/Portal'; jest.useRealTimers(); @@ -21,3 +23,49 @@ it('renders portal with siblings', async () => { expect(toJSON()).toMatchSnapshot(); }); + +it('renders portals in source order when mounted in the same commit', async () => { + await render( + + + first + + + second + + + third + + + ); + + const portals = await screen.findAllByTestId('portal-content'); + + expect(portals).toHaveLength(3); + expect(portals[0]).toHaveTextContent('first'); + expect(portals[1]).toHaveTextContent('second'); + expect(portals[2]).toHaveTextContent('third'); +}); + +it('stacks components mounted in the same commit in source order', async () => { + await render( + + + {}}> + modal + + + + {}}> + dialog + + + + ); + + const layers = await screen.findAllByTestId('layer'); + + expect(layers).toHaveLength(2); + expect(layers[0]).toHaveTextContent('modal'); + expect(layers[1]).toHaveTextContent('dialog'); +});