Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 140 additions & 0 deletions src/content/reference/react-dom/browser.md
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,146 @@ function ProductDetails({ productId, initialData }) {

On the server, `useBrowserQuery` calls `useQuery` only when `initialData` is available. Otherwise, the closest Suspense boundary's fallback remains in the HTML. In the browser, `use(browser())` returns `undefined`, so the query library can fetch the data or read it from its client cache.

You can also call `use(browser())` only when initial data isn't available. In this example, the event time zone is provided as initial data, but the user's time zone is read from the browser. Click **Reload** to see the loading fallback for the user's time zone.

<Sandpack>

```js src/App.js
import { Suspense } from 'react';
import { useTimeZone } from './useTimeZone.js';

function TimeZone({label, initialTimeZone}) {
const timeZone = useTimeZone(initialTimeZone);
return <p>{label}: <strong>{timeZone}</strong></p>;
}

export default function App() {
return (
<>
<h1>Event details</h1>
<TimeZone
label="Event time zone"
initialTimeZone="America/New_York"
/>
<Suspense fallback={<p>Loading your time zone...</p>}>
<TimeZone label="Your time zone" />
</Suspense>
</>
);
}
```

```js src/useTimeZone.js active
import { use } from 'react';
import { browser } from 'react-dom';

export function useTimeZone(initialTimeZone) {
if (initialTimeZone !== undefined) {
return initialTimeZone;
}

use(browser('No initial time zone was provided.'));
return Intl.DateTimeFormat().resolvedOptions().timeZone;
}
```

```js src/Document.js hidden
import App from './App.js';

export default function Document() {
return (
<html lang="en">
<head>
<title>Event details</title>
<style>{`
h1 { font-size: 24px; margin-top: 0; }
`}</style>
</head>
<body>
<App />
</body>
</html>
);
}
```

```js src/index.js hidden
import { hydrateRoot } from 'react-dom/client';
import { renderToReadableStream } from 'react-dom/server';
import Document from './Document.js';
import { flushReadableStreamToFrame } from './demo-helpers.js';
import './styles.css';

async function main(frame) {
const stream = await renderToReadableStream(<Document />);
await flushReadableStreamToFrame(stream, frame);

// Wait so both the fallback and hydrated content are visible.
await new Promise(resolve => setTimeout(resolve, 1200));
hydrateRoot(frame.contentDocument, <Document />);
}

main(document.getElementById('preview'));
```

```js src/demo-helpers.js hidden
export async function flushReadableStreamToFrame(readable, frame) {
const doc = frame.contentWindow.document;
const decoder = new TextDecoder();
const reader = readable.getReader();

while (true) {
const {done, value} = await reader.read();
if (done) {
break;
}
doc.write(decoder.decode(value, {stream: true}));
}

doc.write(decoder.decode());
doc.close();
}
```

```html public/index.html hidden
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Conditional browser rendering</title>
</head>
<body>
<iframe id="preview" title="Rendered page"></iframe>
</body>
</html>
```

```css src/styles.css hidden
iframe {
width: 100%;
height: 240px;
border: 0;
}
```

```json package.json hidden
{
"dependencies": {
"react": "19.3.0-canary-eb8feb71-20260814",
"react-dom": "19.3.0-canary-eb8feb71-20260814",
"react-scripts": "latest"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
}
}
```

</Sandpack>

---

### Reporting browser-only rendering on the server {/*reporting-browser-only-rendering-on-the-server*/}
Expand Down
Loading