Skip to content

Commit 385d306

Browse files
committed
Polish browser API docs and add live example
1 parent a17d58c commit 385d306

2 files changed

Lines changed: 138 additions & 25 deletions

File tree

src/content/reference/react-dom/browser.md

Lines changed: 137 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ title: browser
33
version: canary
44
---
55

6+
<Intro>
7+
68
<Canary>
79

810
**The `browser` API is currently only available in React’s Canary and Experimental channels.**
@@ -11,12 +13,10 @@ version: canary
1113

1214
</Canary>
1315

14-
<Intro>
15-
16-
`browser` lets you skip rendering part of a React tree on the server, leaving its nearest Suspense fallback in place until that content renders in the browser.
16+
`browser` lets you render part of a React tree only in the browser.
1717

1818
```js
19-
use(browser(reason?));
19+
use(browser(reason?))
2020
```
2121
2222
</Intro>
@@ -41,24 +41,24 @@ function BrowserOnly() {
4141
}
4242
```
4343
44-
During server rendering, `use(browser())` stops rendering the component and renders the fallback of the closest [`<Suspense>`](/reference/react/Suspense) boundary instead. In the browser, it has no effect, so the component renders normally.
44+
During server rendering, `use(browser())` stops rendering the component and leaves the closest [`<Suspense>`](/reference/react/Suspense) boundary's fallback in its place. In the browser, `use(browser())` returns `undefined`, so the component renders normally.
4545
4646
[See more examples below.](#usage)
4747
4848
#### Parameters {/*parameters*/}
4949
50-
* **optional** `reason`: A string or function that provides diagnostic information about why rendering should happen only in the browser. React calls a reason function each time a server renderer encounters the value returned by `browser`; it never calls it in the browser. Use a function for values that are expensive to create, such as `() => new Error(...)`. The resulting value becomes the `cause` of the `Error` passed to `onBrowserBailout`.
50+
* **optional** `reason`: A string or function that explains why the content needs to render in the browser. If you pass a function, React calls it each time a server renderer encounters the value returned by `browser`. React does not call it in the browser. Use a function for values that are expensive to create, such as `() => new Error(...)`. The string or the function's return value becomes the `cause` of the `Error` passed to `onBrowserBailout`.
5151
5252
#### Returns {/*returns*/}
5353
54-
`browser` returns an opaque value. Pass this value to `use` in a component, or use it as the reason when [aborting a server render](#aborting-pending-server-rendering-for-the-browser). In the browser, passing this value to `use` returns `undefined`.
54+
`browser` returns a value that you can pass to `use` in a component or use as the reason when [aborting a server render](#aborting-pending-server-rendering-for-the-browser). In the browser, passing this value to `use` returns `undefined`.
5555
5656
#### Caveats {/*caveats*/}
5757
58-
* A component that passes a value returned by `browser` to `use` during server rendering must have a `<Suspense>` boundary above it. Otherwise, the entire server render will fail.
59-
* `browser` is not available in a `react-server` environment. You can use it while server-rendering Client Components, but you cannot import it in a [React Server Component](/reference/rsc/server-components).
60-
* Calling `browser()` by itself does not check the current environment or affect rendering. To trigger its behavior, pass the return value to `use` or use it to abort a server render. This means you can create the value at module scope and reuse it.
61-
* To defer a component, pass the value returned by `browser` to `use`. Do not throw the value directly.
58+
* `use(browser())` must be inside a `<Suspense>` boundary during server rendering. Without one, the server render fails.
59+
* `browser` is not available in a `react-server` environment. You can use it while rendering Client Components on the server, but you cannot import it in a [React Server Component](/reference/rsc/server-components).
60+
* Calling `browser()` by itself has no effect. You can create the value at module scope and reuse it.
61+
* To skip rendering a component on the server, pass the value returned by `browser` to `use`. Do not throw it.
6262
6363
---
6464
@@ -68,16 +68,20 @@ During server rendering, `use(browser())` stops rendering the component and rend
6868
6969
Call `use` with the value returned by `browser` to skip rendering a component on the server:
7070
71-
```js
71+
Press **Render on the server** to see the fallback first. The demo waits briefly before hydrating and showing the browser-only editor.
72+
73+
<Sandpack>
74+
75+
```js src/App.js active
7276
import { Suspense, use } from 'react';
7377
import { browser } from 'react-dom';
7478

7579
function BrowserOnlyEditor() {
7680
use(browser('The editor requires browser APIs.'));
77-
return <Editor />;
81+
return <label>Draft: <input /></label>;
7882
}
7983

80-
export default function Page() {
84+
export default function App() {
8185
return (
8286
<Suspense fallback={<p>Loading editor...</p>}>
8387
<BrowserOnlyEditor />
@@ -86,13 +90,122 @@ export default function Page() {
8690
}
8791
```
8892
89-
During server rendering, React includes the `Loading editor...` fallback in the HTML. When the app renders in the browser, `use(browser())` continues immediately and React renders the `Editor` instead.
93+
```js src/Document.js hidden
94+
import App from './App.js';
95+
96+
export default function Document() {
97+
return (
98+
<html lang="en">
99+
<head>
100+
<title>Article editor</title>
101+
</head>
102+
<body>
103+
<h1>Article editor</h1>
104+
<App />
105+
</body>
106+
</html>
107+
);
108+
}
109+
```
110+
111+
```js src/index.js
112+
import { hydrateRoot } from 'react-dom/client';
113+
import { renderToReadableStream } from 'react-dom/server';
114+
import Document from './Document.js';
115+
import { flushReadableStreamToFrame } from './demo-helpers.js';
116+
import './styles.css';
117+
118+
async function main(frame) {
119+
const stream = await renderToReadableStream(<Document />);
120+
await flushReadableStreamToFrame(stream, frame);
121+
122+
// Wait so both the fallback and hydrated content are visible.
123+
await new Promise(resolve => setTimeout(resolve, 1200));
124+
hydrateRoot(frame.contentDocument, <Document />);
125+
}
126+
127+
const renderButton = document.getElementById('render');
128+
renderButton.addEventListener('click', () => {
129+
renderButton.disabled = true;
130+
main(document.getElementById('preview'));
131+
}, { once: true });
132+
```
133+
134+
```js src/demo-helpers.js hidden
135+
export async function flushReadableStreamToFrame(readable, frame) {
136+
const doc = frame.contentWindow.document;
137+
const decoder = new TextDecoder();
138+
for await (const chunk of readable) {
139+
doc.write(decoder.decode(chunk, { stream: true }));
140+
}
141+
doc.close();
142+
}
143+
```
144+
145+
```html public/index.html
146+
<!DOCTYPE html>
147+
<html lang="en">
148+
<head>
149+
<meta charset="UTF-8" />
150+
<title>Browser-only rendering</title>
151+
</head>
152+
<body>
153+
<button id="render">Render on the server</button>
154+
<br /><br />
155+
<iframe id="preview" title="Rendered page"></iframe>
156+
</body>
157+
</html>
158+
```
159+
160+
```css src/styles.css hidden
161+
iframe {
162+
width: 100%;
163+
height: 180px;
164+
border: 1px solid #aaa;
165+
}
166+
```
167+
168+
```json package.json hidden
169+
{
170+
"dependencies": {
171+
"react": "canary",
172+
"react-dom": "canary",
173+
"react-scripts": "latest"
174+
},
175+
"scripts": {
176+
"start": "react-scripts start",
177+
"build": "react-scripts build",
178+
"test": "react-scripts test --env=jsdom",
179+
"eject": "react-scripts eject"
180+
}
181+
}
182+
```
183+
184+
</Sandpack>
185+
186+
<Note>
187+
188+
In a React Server Components app, `use(browser())` must be called from a Client Component. If your framework uses Server Components by default, add the [`'use client'`](/reference/rsc/use-client) directive to that file or move the call to a child Client Component:
189+
190+
```js {1}
191+
'use client';
192+
193+
import { use } from 'react';
194+
import { browser } from 'react-dom';
195+
196+
export default function BrowserOnlyEditor() {
197+
use(browser('The editor requires browser APIs.'));
198+
return <Editor />;
199+
}
200+
```
201+
202+
</Note>
90203
91204
---
92205
93206
### Conditionally rendering in the browser {/*conditionally-rendering-in-the-browser*/}
94207
95-
Like other calls to [`use`](/reference/react/use), `use(browser())` can be called conditionally, including inside a custom Hook. For example, you can wrap a Suspense-enabled data-fetching library's `useQuery` to render initial data on the server, but defer to the browser when that data is missing:
208+
Like other calls to [`use`](/reference/react/use), you can call `use(browser())` conditionally or inside a custom Hook. For example, you can wrap a Suspense-enabled data-fetching library's `useQuery` and skip server rendering when initial data is missing:
96209
97210
```js {3}
98211
function useBrowserQuery(query, options) {
@@ -112,13 +225,13 @@ function ProductDetails({ productId, initialData }) {
112225
}
113226
```
114227
115-
On the server, `useBrowserQuery` calls the underlying `useQuery` only when `initialData` is available. Otherwise, `use(browser())` leaves the nearest Suspense fallback in the HTML. In the browser, `use(browser())` continues immediately, so the query library can fetch the data or read it from its client cache.
228+
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.
116229
117230
---
118231
119232
### Reporting browser-only rendering on the server {/*reporting-browser-only-rendering-on-the-server*/}
120233
121-
Provide `onBrowserBailout` to the server renderer to report browser-only rendering. React does not report a browser-only render recovered by a Suspense boundary to the server renderer's `onError` callback or [`hydrateRoot`'s `onRecoverableError`](/reference/react-dom/client/hydrateRoot#error-logging-in-production) callback. This example also passes an optional reason, which React makes available as the reported error's `cause`:
234+
Pass an `onBrowserBailout` callback to the server renderer to report browser-only rendering. When React leaves a Suspense fallback for the browser, it does not call the server renderer's `onError` callback or [`hydrateRoot`'s `onRecoverableError`](/reference/react-dom/client/hydrateRoot#error-logging-in-production) callback. This example also passes a reason, which is available as the reported error's `cause`:
122235
123236
```js
124237
import { Suspense, use } from 'react';
@@ -147,18 +260,18 @@ const { pipe } = renderToPipeableStream(
147260
148261
`onBrowserBailout` receives two arguments:
149262
150-
1. An `Error` describing the browser-only render. If a reason was supplied to `browser`, it is available as the error's `cause`.
151-
2. An `errorInfo` object containing the `componentStack` of the browser-only render.
263+
1. An `Error` describing the browser-only render. If you passed a reason to `browser`, it is available as the error's `cause`.
264+
2. An `errorInfo` object with a `componentStack` showing where browser-only rendering occurred.
152265
153-
The reason function can return any value. Returning a new `Error` gives the cause its own stack without creating that `Error` during rendering in the browser. React does not serialize the reason into the HTML.
266+
The reason function can return any value. Return a new `Error` to give the cause its own stack without creating the `Error` in the browser. React does not serialize the reason into the HTML.
154267
155-
If there is no Suspense boundary to provide a fallback, the server render fails. React reports the failure through the renderer's normal error callbacks instead of `onBrowserBailout`.
268+
If there is no Suspense boundary to provide a fallback, the server render fails. React reports the failure through the renderer's usual error callbacks instead of `onBrowserBailout`.
156269
157270
---
158271
159272
### Aborting pending server rendering for the browser {/*aborting-pending-server-rendering-for-the-browser*/}
160273
161-
You can pass the value returned by `browser` as the reason for aborting a server render. This leaves pending Suspense boundaries in their fallback state so React can render their content in the browser:
274+
Pass the value returned by `browser` as the reason when aborting a server render. React then leaves pending Suspense boundaries in their fallback state and renders their content in the browser:
162275
163276
```js {1,8}
164277
import { browser } from 'react-dom';
@@ -174,6 +287,6 @@ const { pipe, abort } = renderToPipeableStream(<App />, {
174287
});
175288
```
176289
177-
Unlike other abort reasons, a value returned by `browser` is not reported to the server renderer's `onError` callback or to `hydrateRoot`'s `onRecoverableError` callback. The server renderer reports each recovered Suspense boundary to `onBrowserBailout` instead.
290+
A `browser` abort reason does not trigger the server renderer's `onError` callback or `hydrateRoot`'s `onRecoverableError` callback. Instead, the server renderer reports each recovered Suspense boundary to `onBrowserBailout`.
178291
179292
For server rendering APIs that accept an [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal), pass `browser()` as the reason to [`AbortController.abort`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController/abort).

src/content/reference/react-dom/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ These APIs can be used to make apps faster by pre-loading resources such as scri
3434

3535
This API controls how components render on the server:
3636

37-
* <CanaryBadge /> [`browser`](/reference/react-dom/browser) lets you skip rendering part of a React tree on the server, leaving its nearest Suspense fallback in place until that content renders in the browser.
37+
* <CanaryBadge /> [`browser`](/reference/react-dom/browser) lets you render part of a React tree only in the browser.
3838

3939
---
4040

0 commit comments

Comments
 (0)