From 9a97f419d20eca2d35e09aeb89e2ceda18547419 Mon Sep 17 00:00:00 2001
From: Aurora Scharff
Date: Thu, 20 Aug 2026 23:51:59 +0200
Subject: [PATCH 1/8] Add conditional browser query example
---
src/content/reference/react-dom/browser.md | 176 +++++++++++++++++++++
1 file changed, 176 insertions(+)
diff --git a/src/content/reference/react-dom/browser.md b/src/content/reference/react-dom/browser.md
index 017da34e7d2..495f2afeaa7 100644
--- a/src/content/reference/react-dom/browser.md
+++ b/src/content/reference/react-dom/browser.md
@@ -258,6 +258,182 @@ 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.
+This example renders one query with initial data and one without it.
+
+Click **Reload** to see the second product's loading fallback before its query resolves.
+
+
+
+```js src/App.js active
+import { Suspense, use } from 'react';
+import { browser } from 'react-dom';
+import { useQuery } from './query.js';
+
+function useBrowserQuery(query, options) {
+ if (options.initialData === undefined) {
+ use(browser('useBrowserQuery: No initial data was provided.'));
+ }
+ return useQuery(query, options);
+}
+
+function ProductDetails({productId, initialData}) {
+ const product = useBrowserQuery(`/api/products/${productId}`, {
+ initialData,
+ });
+ return {product.name};
+}
+
+export default function App() {
+ return (
+ <>
+ Featured products
+
+ -
+
+
+ Loading another product...}>
+ -
+
+
+
+
+ >
+ );
+}
+```
+
+```js src/query.js hidden
+import { use } from 'react';
+
+// This is a simplified implementation of a
+// Suspense-enabled query library.
+
+const products = {
+ '/api/products/react-shirt': {name: 'React shirt'},
+};
+
+const cache = new Map();
+
+function fetchProduct(query) {
+ if (!cache.has(query)) {
+ cache.set(
+ query,
+ new Promise(resolve => {
+ setTimeout(() => resolve(products[query]), 600);
+ })
+ );
+ }
+ return cache.get(query);
+}
+
+export function useQuery(query, options) {
+ if (options.initialData !== undefined) {
+ return options.initialData;
+ }
+ return use(fetchProduct(query));
+}
+```
+
+```js src/Document.js hidden
+import App from './App.js';
+
+export default function Document() {
+ return (
+
+
+ Featured products
+
+
+
+
+
+
+ );
+}
+```
+
+```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();
+ await flushReadableStreamToFrame(stream, frame);
+
+ // Wait so both the fallback and hydrated content are visible.
+ await new Promise(resolve => setTimeout(resolve, 1200));
+ hydrateRoot(frame.contentDocument, );
+}
+
+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
+
+
+
+
+ Conditional browser rendering
+
+
+
+
+
+```
+
+```css src/styles.css hidden
+iframe {
+ width: 100%;
+ height: 170px;
+ 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"
+ }
+}
+```
+
+
+
---
### Reporting browser-only rendering on the server {/*reporting-browser-only-rendering-on-the-server*/}
From 9247c01776db1e48aee9876685d202c75ca7a952 Mon Sep 17 00:00:00 2001
From: Aurora Scharff
Date: Mon, 24 Aug 2026 13:12:25 +0200
Subject: [PATCH 2/8] Move browser query hook into its own example file
---
src/content/reference/react-dom/browser.md | 25 +++++++++++++---------
1 file changed, 15 insertions(+), 10 deletions(-)
diff --git a/src/content/reference/react-dom/browser.md b/src/content/reference/react-dom/browser.md
index 495f2afeaa7..a08fe807ba6 100644
--- a/src/content/reference/react-dom/browser.md
+++ b/src/content/reference/react-dom/browser.md
@@ -265,16 +265,8 @@ Click **Reload** to see the second product's loading fallback before its query r
```js src/App.js active
-import { Suspense, use } from 'react';
-import { browser } from 'react-dom';
-import { useQuery } from './query.js';
-
-function useBrowserQuery(query, options) {
- if (options.initialData === undefined) {
- use(browser('useBrowserQuery: No initial data was provided.'));
- }
- return useQuery(query, options);
-}
+import { Suspense } from 'react';
+import { useBrowserQuery } from './useBrowserQuery.js';
function ProductDetails({productId, initialData}) {
const product = useBrowserQuery(`/api/products/${productId}`, {
@@ -305,6 +297,19 @@ export default function App() {
}
```
+```js src/useBrowserQuery.js
+import { use } from 'react';
+import { browser } from 'react-dom';
+import { useQuery } from './query.js';
+
+export function useBrowserQuery(query, options) {
+ if (options.initialData === undefined) {
+ use(browser('useBrowserQuery: No initial data was provided.'));
+ }
+ return useQuery(query, options);
+}
+```
+
```js src/query.js hidden
import { use } from 'react';
From 262b84a663fac9000d83cb5e47c5841ba0108e9e Mon Sep 17 00:00:00 2001
From: Aurora Scharff
Date: Mon, 24 Aug 2026 19:41:18 +0200
Subject: [PATCH 3/8] Use IndexedDB in conditional browser example
---
src/content/reference/react-dom/browser.md | 125 +++++++++++----------
1 file changed, 63 insertions(+), 62 deletions(-)
diff --git a/src/content/reference/react-dom/browser.md b/src/content/reference/react-dom/browser.md
index a08fe807ba6..61faee3fd3f 100644
--- a/src/content/reference/react-dom/browser.md
+++ b/src/content/reference/react-dom/browser.md
@@ -236,60 +236,53 @@ export default function SavedDraft() {
### Conditionally rendering in the browser {/*conditionally-rendering-in-the-browser*/}
-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:
+Like other calls to [`use`](/reference/react/use), you can call `use(browser())` conditionally or inside a custom Hook. For example, a custom Hook can use initial data when it is available during server rendering, and read from IndexedDB in the browser when it isn't:
```js {3}
-function useBrowserQuery(query, options) {
- if (options.initialData === undefined) {
- use(browser('useBrowserQuery: No initial data was provided.'));
+function useDraft(draftId, initialDraft) {
+ if (initialDraft !== undefined) {
+ return initialDraft;
}
- return useQuery(query, options);
-}
-
-function ProductDetails({ productId, initialData }) {
- const product = useBrowserQuery(`/api/products/${productId}`, {
- initialData,
- });
-
- return {product.name}
;
+ use(browser('The draft is stored in IndexedDB.'));
+ return use(readDraft(draftId));
}
```
-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.
+On the server, `useDraft` returns `initialDraft` when it is provided. Otherwise, the closest Suspense boundary's fallback remains in the HTML. In the browser, `use(browser())` returns `undefined`, so the Hook continues and reads the draft from IndexedDB.
-This example renders one query with initial data and one without it.
+This example renders one draft with initial data and one stored in IndexedDB.
-Click **Reload** to see the second product's loading fallback before its query resolves.
+Click **Reload** to see the second draft's loading fallback while React reads it from IndexedDB.
```js src/App.js active
import { Suspense } from 'react';
-import { useBrowserQuery } from './useBrowserQuery.js';
+import { useDraft } from './useDraft.js';
-function ProductDetails({productId, initialData}) {
- const product = useBrowserQuery(`/api/products/${productId}`, {
- initialData,
- });
- return {product.name};
+function Draft({draftId, title, initialDraft}) {
+ const draft = useDraft(draftId, initialDraft);
+ return (
+
+ {title}
+ {draft}
+
+ );
}
export default function App() {
return (
<>
- Featured products
+ Saved drafts
- -
-
-
- Loading another product...}>
- -
-
-
+
+ Loading saved draft...}>
+
>
@@ -297,48 +290,56 @@ export default function App() {
}
```
-```js src/useBrowserQuery.js
+```js src/useDraft.js
import { use } from 'react';
import { browser } from 'react-dom';
-import { useQuery } from './query.js';
+import { readDraft } from './database.js';
-export function useBrowserQuery(query, options) {
- if (options.initialData === undefined) {
- use(browser('useBrowserQuery: No initial data was provided.'));
+export function useDraft(draftId, initialDraft) {
+ if (initialDraft !== undefined) {
+ return initialDraft;
}
- return useQuery(query, options);
+
+ use(browser('The draft is stored in IndexedDB.'));
+ return use(readDraft(draftId));
}
```
-```js src/query.js hidden
-import { use } from 'react';
-
-// This is a simplified implementation of a
-// Suspense-enabled query library.
-
-const products = {
- '/api/products/react-shirt': {name: 'React shirt'},
+```js src/database.js hidden
+const drafts = {
+ 'trip-notes': 'Remember to pack a charger.',
};
const cache = new Map();
-function fetchProduct(query) {
- if (!cache.has(query)) {
- cache.set(
- query,
- new Promise(resolve => {
- setTimeout(() => resolve(products[query]), 600);
- })
- );
+export function readDraft(draftId) {
+ if (!cache.has(draftId)) {
+ cache.set(draftId, readDraftFromIndexedDB(draftId));
}
- return cache.get(query);
+ return cache.get(draftId);
}
-export function useQuery(query, options) {
- if (options.initialData !== undefined) {
- return options.initialData;
- }
- return use(fetchProduct(query));
+function readDraftFromIndexedDB(draftId) {
+ return new Promise((resolve, reject) => {
+ const request = indexedDB.open('browser-example', 1);
+
+ request.onupgradeneeded = () => {
+ const store = request.result.createObjectStore('drafts');
+ for (const [key, value] of Object.entries(drafts)) {
+ store.add(value, key);
+ }
+ };
+
+ request.onerror = () => reject(request.error);
+ request.onsuccess = () => {
+ const transaction = request.result.transaction('drafts');
+ const draftRequest = transaction.objectStore('drafts').get(draftId);
+ draftRequest.onerror = () => reject(draftRequest.error);
+ draftRequest.onsuccess = () => {
+ setTimeout(() => resolve(draftRequest.result), 600);
+ };
+ };
+ });
}
```
@@ -349,7 +350,7 @@ export default function Document() {
return (
- Featured products
+ Saved drafts
From 4bb86b6e8b4f4a944f55a538f12055c142d0fddf Mon Sep 17 00:00:00 2001
From: Aurora Scharff
Date: Mon, 24 Aug 2026 19:49:54 +0200
Subject: [PATCH 4/8] Clarify conditional browser rendering example
---
src/content/reference/react-dom/browser.md | 96 ++++++++++------------
1 file changed, 45 insertions(+), 51 deletions(-)
diff --git a/src/content/reference/react-dom/browser.md b/src/content/reference/react-dom/browser.md
index 61faee3fd3f..9869bbd3fdb 100644
--- a/src/content/reference/react-dom/browser.md
+++ b/src/content/reference/react-dom/browser.md
@@ -236,37 +236,34 @@ export default function SavedDraft() {
### Conditionally rendering in the browser {/*conditionally-rendering-in-the-browser*/}
-Like other calls to [`use`](/reference/react/use), you can call `use(browser())` conditionally or inside a custom Hook. For example, a custom Hook can use initial data when it is available during server rendering, and read from IndexedDB in the browser when it isn't:
+Like other calls to [`use`](/reference/react/use), you can call `use(browser())` conditionally or inside a custom Hook. For example, a custom Hook can return an initial value when it is provided, and read it from IndexedDB in the browser when it isn't:
-```js {3}
-function useDraft(draftId, initialDraft) {
- if (initialDraft !== undefined) {
- return initialDraft;
+```js {6}
+function useSetting(settingId, initialValue) {
+ if (initialValue !== undefined) {
+ return initialValue;
}
- use(browser('The draft is stored in IndexedDB.'));
- return use(readDraft(draftId));
+ use(browser('No initial setting was provided.'));
+ return use(readSetting(settingId));
}
```
-On the server, `useDraft` returns `initialDraft` when it is provided. Otherwise, the closest Suspense boundary's fallback remains in the HTML. In the browser, `use(browser())` returns `undefined`, so the Hook continues and reads the draft from IndexedDB.
+On the server, `useSetting` returns `initialValue` when it is provided. Otherwise, the closest Suspense boundary's fallback remains in the HTML. In the browser, `use(browser())` returns `undefined`, so the Hook continues and reads the setting from IndexedDB.
-This example renders one draft with initial data and one stored in IndexedDB.
-
-Click **Reload** to see the second draft's loading fallback while React reads it from IndexedDB.
+In this example, the email notification setting is provided as initial data. The push notification setting is not, so click **Reload** to see its loading fallback while React reads it from IndexedDB.
```js src/App.js active
import { Suspense } from 'react';
-import { useDraft } from './useDraft.js';
+import { useSetting } from './useSetting.js';
-function Draft({draftId, title, initialDraft}) {
- const draft = useDraft(draftId, initialDraft);
+function NotificationSetting({settingId, label, initialValue}) {
+ const enabled = useSetting(settingId, initialValue);
return (
- {title}
- {draft}
+ {label}: {enabled ? 'On' : 'Off'}
);
}
@@ -274,15 +271,18 @@ function Draft({draftId, title, initialDraft}) {
export default function App() {
return (
<>
- Saved drafts
+ Notification settings
-
- Loading saved draft...}>
-
+ Loading push notification setting...}>
+
>
@@ -290,53 +290,47 @@ export default function App() {
}
```
-```js src/useDraft.js
+```js src/useSetting.js
import { use } from 'react';
import { browser } from 'react-dom';
-import { readDraft } from './database.js';
+import { readSetting } from './database.js';
-export function useDraft(draftId, initialDraft) {
- if (initialDraft !== undefined) {
- return initialDraft;
+export function useSetting(settingId, initialValue) {
+ if (initialValue !== undefined) {
+ return initialValue;
}
- use(browser('The draft is stored in IndexedDB.'));
- return use(readDraft(draftId));
+ use(browser('No initial setting was provided.'));
+ return use(readSetting(settingId));
}
```
```js src/database.js hidden
-const drafts = {
- 'trip-notes': 'Remember to pack a charger.',
-};
-
const cache = new Map();
-export function readDraft(draftId) {
- if (!cache.has(draftId)) {
- cache.set(draftId, readDraftFromIndexedDB(draftId));
+export function readSetting(settingId) {
+ if (!cache.has(settingId)) {
+ cache.set(settingId, readSettingFromIndexedDB(settingId));
}
- return cache.get(draftId);
+ return cache.get(settingId);
}
-function readDraftFromIndexedDB(draftId) {
+function readSettingFromIndexedDB(settingId) {
return new Promise((resolve, reject) => {
- const request = indexedDB.open('browser-example', 1);
+ const request = indexedDB.open('browser-notification-settings-example', 1);
request.onupgradeneeded = () => {
- const store = request.result.createObjectStore('drafts');
- for (const [key, value] of Object.entries(drafts)) {
- store.add(value, key);
- }
+ const store = request.result.createObjectStore('settings');
+ store.add(true, 'push');
};
request.onerror = () => reject(request.error);
request.onsuccess = () => {
- const transaction = request.result.transaction('drafts');
- const draftRequest = transaction.objectStore('drafts').get(draftId);
- draftRequest.onerror = () => reject(draftRequest.error);
- draftRequest.onsuccess = () => {
- setTimeout(() => resolve(draftRequest.result), 600);
+ const transaction = request.result.transaction('settings');
+ const settingRequest = transaction.objectStore('settings').get(settingId);
+ settingRequest.onerror = () => reject(settingRequest.error);
+ settingRequest.onsuccess = () => {
+ setTimeout(() => resolve(settingRequest.result), 600);
};
};
});
@@ -350,7 +344,7 @@ export default function Document() {
return (
- Saved drafts
+ Notification settings
@@ -417,7 +411,7 @@ export async function flushReadableStreamToFrame(readable, frame) {
```css src/styles.css hidden
iframe {
width: 100%;
- height: 170px;
+ height: 240px;
border: 0;
}
```
From fd80b33b9081dadc6bc9a77c65cd1fde1698280d Mon Sep 17 00:00:00 2001
From: Aurora Scharff
Date: Mon, 24 Aug 2026 19:57:02 +0200
Subject: [PATCH 5/8] Polish browser rendering examples
---
src/content/reference/react-dom/browser.md | 31 ++++++++++++++--------
1 file changed, 20 insertions(+), 11 deletions(-)
diff --git a/src/content/reference/react-dom/browser.md b/src/content/reference/react-dom/browser.md
index 9869bbd3fdb..6683d8e2002 100644
--- a/src/content/reference/react-dom/browser.md
+++ b/src/content/reference/react-dom/browser.md
@@ -236,26 +236,33 @@ export default function SavedDraft() {
### Conditionally rendering in the browser {/*conditionally-rendering-in-the-browser*/}
-Like other calls to [`use`](/reference/react/use), you can call `use(browser())` conditionally or inside a custom Hook. For example, a custom Hook can return an initial value when it is provided, and read it from IndexedDB in the browser when it isn't:
+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:
-```js {6}
-function useSetting(settingId, initialValue) {
- if (initialValue !== undefined) {
- return initialValue;
+```js {3}
+function useBrowserQuery(query, options) {
+ if (options.initialData === undefined) {
+ use(browser('useBrowserQuery: No initial data was provided.'));
}
- use(browser('No initial setting was provided.'));
- return use(readSetting(settingId));
+ return useQuery(query, options);
+}
+
+function ProductDetails({ productId, initialData }) {
+ const product = useBrowserQuery(`/api/products/${productId}`, {
+ initialData,
+ });
+
+ return {product.name}
;
}
```
-On the server, `useSetting` returns `initialValue` when it is provided. Otherwise, the closest Suspense boundary's fallback remains in the HTML. In the browser, `use(browser())` returns `undefined`, so the Hook continues and reads the setting from IndexedDB.
+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.
-In this example, the email notification setting is provided as initial data. The push notification setting is not, so click **Reload** to see its loading fallback while React reads it from IndexedDB.
+Here is a complete example using notification settings stored in IndexedDB. The email notification setting receives initial data, but the push notification setting does not. Click **Reload** to see the email setting in the initial HTML while the push setting shows a loading fallback.
-```js src/App.js active
+```js src/App.js
import { Suspense } from 'react';
import { useSetting } from './useSetting.js';
@@ -290,7 +297,7 @@ export default function App() {
}
```
-```js src/useSetting.js
+```js src/useSetting.js active
import { use } from 'react';
import { browser } from 'react-dom';
import { readSetting } from './database.js';
@@ -306,6 +313,8 @@ export function useSetting(settingId, initialValue) {
```
```js src/database.js hidden
+// This is a simplified IndexedDB wrapper for this example.
+
const cache = new Map();
export function readSetting(settingId) {
From 650028b8846209a7fbe5f6f0b9a637d5b6b77534 Mon Sep 17 00:00:00 2001
From: Aurora Scharff
Date: Mon, 24 Aug 2026 21:59:09 +0200
Subject: [PATCH 6/8] Bridge conditional browser examples
---
src/content/reference/react-dom/browser.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/content/reference/react-dom/browser.md b/src/content/reference/react-dom/browser.md
index 6683d8e2002..5234b4034dd 100644
--- a/src/content/reference/react-dom/browser.md
+++ b/src/content/reference/react-dom/browser.md
@@ -258,7 +258,7 @@ 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.
-Here is a complete example using notification settings stored in IndexedDB. The email notification setting receives initial data, but the push notification setting does not. Click **Reload** to see the email setting in the initial HTML while the push setting shows a loading fallback.
+Another way to use conditional `use(browser())` is to read from a browser-only data source when initial data is unavailable. In this example, `useSetting` receives an initial value for the email notification setting, so React can include it in the initial HTML. The push notification setting has no initial value, so `useSetting` calls `use(browser())` before reading it from IndexedDB. Click **Reload** to see both paths: the email setting appears immediately, while the push setting shows a loading fallback until `useSetting` reads it from IndexedDB in the browser.
From 44b7dd58ca2c40dceb6eed54ba8a145a3efb36a0 Mon Sep 17 00:00:00 2001
From: Aurora Scharff
Date: Mon, 24 Aug 2026 22:01:13 +0200
Subject: [PATCH 7/8] Tighten conditional browser example intro
---
src/content/reference/react-dom/browser.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/content/reference/react-dom/browser.md b/src/content/reference/react-dom/browser.md
index 5234b4034dd..ef7904b6ae1 100644
--- a/src/content/reference/react-dom/browser.md
+++ b/src/content/reference/react-dom/browser.md
@@ -258,7 +258,7 @@ 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.
-Another way to use conditional `use(browser())` is to read from a browser-only data source when initial data is unavailable. In this example, `useSetting` receives an initial value for the email notification setting, so React can include it in the initial HTML. The push notification setting has no initial value, so `useSetting` calls `use(browser())` before reading it from IndexedDB. Click **Reload** to see both paths: the email setting appears immediately, while the push setting shows a loading fallback until `useSetting` reads it from IndexedDB in the browser.
+You can also use this pattern to read from a browser-only data source when initial data isn't available. In this example, the email setting has initial data, but the push setting is read from IndexedDB. Click **Reload** to see the loading fallback for the push setting.
From fa4a59c60a73c18ba8a8e8fbba40481850c1d696 Mon Sep 17 00:00:00 2001
From: Aurora Scharff
Date: Mon, 24 Aug 2026 22:07:14 +0200
Subject: [PATCH 8/8] Use time zone for conditional browser example
---
src/content/reference/react-dom/browser.md | 85 +++++-----------------
1 file changed, 20 insertions(+), 65 deletions(-)
diff --git a/src/content/reference/react-dom/browser.md b/src/content/reference/react-dom/browser.md
index ef7904b6ae1..b64bbc4ef05 100644
--- a/src/content/reference/react-dom/browser.md
+++ b/src/content/reference/react-dom/browser.md
@@ -258,91 +258,46 @@ 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 use this pattern to read from a browser-only data source when initial data isn't available. In this example, the email setting has initial data, but the push setting is read from IndexedDB. Click **Reload** to see the loading fallback for the push setting.
+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.
```js src/App.js
import { Suspense } from 'react';
-import { useSetting } from './useSetting.js';
+import { useTimeZone } from './useTimeZone.js';
-function NotificationSetting({settingId, label, initialValue}) {
- const enabled = useSetting(settingId, initialValue);
- return (
-
- {label}: {enabled ? 'On' : 'Off'}
-
- );
+function TimeZone({label, initialTimeZone}) {
+ const timeZone = useTimeZone(initialTimeZone);
+ return {label}: {timeZone}
;
}
export default function App() {
return (
<>
- Notification settings
-
-
- Loading push notification setting...}>
-
-
-
+ Event details
+
+ Loading your time zone...
}>
+
+
>
);
}
```
-```js src/useSetting.js active
+```js src/useTimeZone.js active
import { use } from 'react';
import { browser } from 'react-dom';
-import { readSetting } from './database.js';
-export function useSetting(settingId, initialValue) {
- if (initialValue !== undefined) {
- return initialValue;
+export function useTimeZone(initialTimeZone) {
+ if (initialTimeZone !== undefined) {
+ return initialTimeZone;
}
- use(browser('No initial setting was provided.'));
- return use(readSetting(settingId));
-}
-```
-
-```js src/database.js hidden
-// This is a simplified IndexedDB wrapper for this example.
-
-const cache = new Map();
-
-export function readSetting(settingId) {
- if (!cache.has(settingId)) {
- cache.set(settingId, readSettingFromIndexedDB(settingId));
- }
- return cache.get(settingId);
-}
-
-function readSettingFromIndexedDB(settingId) {
- return new Promise((resolve, reject) => {
- const request = indexedDB.open('browser-notification-settings-example', 1);
-
- request.onupgradeneeded = () => {
- const store = request.result.createObjectStore('settings');
- store.add(true, 'push');
- };
-
- request.onerror = () => reject(request.error);
- request.onsuccess = () => {
- const transaction = request.result.transaction('settings');
- const settingRequest = transaction.objectStore('settings').get(settingId);
- settingRequest.onerror = () => reject(settingRequest.error);
- settingRequest.onsuccess = () => {
- setTimeout(() => resolve(settingRequest.result), 600);
- };
- };
- });
+ use(browser('No initial time zone was provided.'));
+ return Intl.DateTimeFormat().resolvedOptions().timeZone;
}
```
@@ -353,7 +308,7 @@ export default function Document() {
return (
- Notification settings
+ Event details