This one is a little contrived, but I've got a minimal reproduction here: https://github.com/alexanderson1993/remix/tree/renderToStream-incorrect-dom-order/demos/renderToStream-incorrect-dom-order
You can run that by checking out that branch, navigating to that demo, running pnpm i and then pnpm dev and visiting the server address.
Here's the setup: You've got an app that's server-rendered with renderToStream. You want to use a browser API, like IndexedDB or Geolocation, so you initialize some component state with null, and then call the browser API inside if (typeof window !== "undefined").
let data: string | null = null
// Simulating running some kind of browser-only API, like IndexedDB or Geolocation
if (typeof window !== 'undefined') {
setTimeout(() => {
data = '🤖'
handle.update()
}, 500)
}
And then in the component itself, we render null in the place where the data should go until hydration completes, the browser API promise resolves, and the component updates.
return () => (
<div>
<div>The robot should render after me</div>
{data ? <div>{data}</div> : null}
<div>
The robot should <strong>not</strong> render after me
</div>
</div>
)
The expected behavior is that you would see this in your browser when everything settles, where the null is replaced with the newly created DOM node.
The robot should render after me
🤖
The robot should not render after me
The actual behavior is that the DOM node is created and appended to the end of the component, like so:
The robot should render after me
The robot should not render after me
🤖
I've tested this with client-only remix/component using createRoot(document.body).render(<App/>) and it behaves as expected. It's only when rendering with renderToStream, clientEntry, and run().
I'll also note that in my example, removing the if (typeof window !== "undefined") guard results in the server crashing with the following error:
/Users/alexanderson/Projects/remix/packages/component/src/lib/component.ts:199
throw new Error('scheduleUpdate not implemented')
^
Error: scheduleUpdate not implemented
at scheduleUpdate (/Users/alexanderson/Projects/remix/packages/component/src/lib/component.ts:199:11)
at <anonymous> (/Users/alexanderson/Projects/remix/packages/component/src/lib/component.ts:214:9)
at new Promise (<anonymous>)
at Object.update (/Users/alexanderson/Projects/remix/packages/component/src/lib/component.ts:212:7)
at Timeout._onTimeout (/Users/alexanderson/Projects/remix/demos/renderToStream-incorrect-dom-order/app/assets/component.tsx:12:14)
at listOnTimeout (node:internal/timers:605:17)
at process.processTimers (node:internal/timers:541:7)
I don't think that's relevant to this particular reproduction, since you would need to have the guard in place if you were using a browser API instead of my contrived setInterval example.
This one is a little contrived, but I've got a minimal reproduction here: https://github.com/alexanderson1993/remix/tree/renderToStream-incorrect-dom-order/demos/renderToStream-incorrect-dom-order
You can run that by checking out that branch, navigating to that demo, running
pnpm iand thenpnpm devand visiting the server address.Here's the setup: You've got an app that's server-rendered with
renderToStream. You want to use a browser API, like IndexedDB or Geolocation, so you initialize some component state withnull, and then call the browser API insideif (typeof window !== "undefined").And then in the component itself, we render
nullin the place where the data should go until hydration completes, the browser API promise resolves, and the component updates.The expected behavior is that you would see this in your browser when everything settles, where the
nullis replaced with the newly created DOM node.The actual behavior is that the DOM node is created and appended to the end of the component, like so:
I've tested this with client-only
remix/componentusingcreateRoot(document.body).render(<App/>)and it behaves as expected. It's only when rendering withrenderToStream,clientEntry, andrun().I'll also note that in my example, removing the
if (typeof window !== "undefined")guard results in the server crashing with the following error:I don't think that's relevant to this particular reproduction, since you would need to have the guard in place if you were using a browser API instead of my contrived
setIntervalexample.