Skip to content
Merged
Show file tree
Hide file tree
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
16 changes: 16 additions & 0 deletions packages/react-debug-tools/src/ReactDebugHooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
import {
REACT_MEMO_CACHE_SENTINEL,
REACT_CONTEXT_TYPE,
REACT_RECOVERABLE_TYPE,
} from 'shared/ReactSymbols';
import hasOwnProperty from 'shared/hasOwnProperty';

Expand Down Expand Up @@ -110,6 +111,11 @@ function getPrimitiveStackCache(): Map<string, Array<any>> {
$$typeof: REACT_CONTEXT_TYPE,
_currentValue: null,
} as any);
const recoverable = new Error();
Object.defineProperty(recoverable as any, '$$typeof', {
value: REACT_RECOVERABLE_TYPE,
});
Dispatcher.use(recoverable as any);
Dispatcher.use({
then() {},
status: 'fulfilled',
Expand Down Expand Up @@ -240,6 +246,16 @@ function use<T>(usable: Usable<T>): T {
dispatcherHookName: 'Use',
});
throw SuspenseException;
} else if (usable.$$typeof === REACT_RECOVERABLE_TYPE) {
hookLog.push({
displayName: null,
primitive: 'Recoverable',
stackError: new Error(),
value: undefined,
debugInfo: null,
dispatcherHookName: 'Use',
});
return undefined as any;
} else if (usable.$$typeof === REACT_CONTEXT_TYPE) {
const context: ReactContext<T> = usable as any;
const value = readContext(context);
Expand Down
31 changes: 19 additions & 12 deletions packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js
Original file line number Diff line number Diff line change
Expand Up @@ -4707,7 +4707,7 @@ export function writeStartClientRenderedSuspenseBoundary(
startClientRenderedSuspenseBoundary,
);
writeChunk(destination, clientRenderedSuspenseBoundaryError1);
if (errorDigest) {
if (errorDigest != null) {
writeChunk(destination, clientRenderedSuspenseBoundaryError1A);
writeChunk(destination, stringToChunk(escapeTextForBrowser(errorDigest)));
writeChunk(
Expand Down Expand Up @@ -5131,6 +5131,7 @@ const clientRenderScript1Full = stringToPrecomputedChunk(
const clientRenderScript1Partial = stringToPrecomputedChunk('$RX("');
const clientRenderScript1A = stringToPrecomputedChunk('"');
const clientRenderErrorScriptArgInterstitial = stringToPrecomputedChunk(',');
const clientRenderErrorScriptNull = stringToPrecomputedChunk('null');
const clientRenderScriptEnd = stringToPrecomputedChunk(')</script>');

const clientRenderData1 = stringToPrecomputedChunk(
Expand Down Expand Up @@ -5182,21 +5183,27 @@ export function writeClientRenderBoundaryInstruction(
writeChunk(destination, clientRenderScript1A);
}

if (errorDigest || errorMessage || errorStack || errorComponentStack) {
if (
errorDigest != null ||
errorMessage ||
errorStack ||
errorComponentStack
) {
if (scriptFormat) {
// ,"JSONString"
// ,null or ,"JSONString"
writeChunk(destination, clientRenderErrorScriptArgInterstitial);
writeChunk(
destination,
stringToChunk(escapeJSStringsForInstructionScripts(errorDigest || '')),
);
} else {
if (errorDigest == null) {
writeChunk(destination, clientRenderErrorScriptNull);
} else {
writeChunk(
destination,
stringToChunk(escapeJSStringsForInstructionScripts(errorDigest)),
);
}
} else if (errorDigest != null) {
// " data-dgst="HTMLString
writeChunk(destination, clientRenderData2);
writeChunk(
destination,
stringToChunk(escapeTextForBrowser(errorDigest || '')),
);
writeChunk(destination, stringToChunk(escapeTextForBrowser(errorDigest)));
}
}
if (errorMessage || errorStack || errorComponentStack) {
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,7 @@ export function clientRenderBoundary(
suspenseNode.data = SUSPENSE_FALLBACK_START_DATA;
// assign error metadata to first sibling
const dataset = suspenseIdNode.dataset;
if (errorDigest) dataset['dgst'] = errorDigest;
if (errorDigest != null) dataset['dgst'] = errorDigest;
if (errorMsg) dataset['msg'] = errorMsg;
if (errorStack) dataset['stck'] = errorStack;
if (errorComponentStack) dataset['cstck'] = errorComponentStack;
Expand Down
1 change: 1 addition & 0 deletions packages/react-dom/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

export {default as __DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE} from './src/ReactDOMSharedInternals';
export {
browser,
createPortal,
flushSync,
prefetchDNS,
Expand Down
1 change: 1 addition & 0 deletions packages/react-dom/src/ReactDOMFB.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Object.assign(Internals as any, {
export {Internals as __DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE};

export {
browser,
createPortal,
flushSync,
unstable_createEventHandle,
Expand Down
1 change: 1 addition & 0 deletions packages/react-dom/src/ReactDOMFB.modern.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
export {default as __DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE} from './ReactDOMSharedInternalsFB';

export {
browser,
createPortal,
flushSync,
unstable_batchedUpdates,
Expand Down
41 changes: 41 additions & 0 deletions packages/react-dom/src/__tests__/ReactDOMBrowser-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @emails react-core
*/

'use strict';

describe('ReactDOM.browser', () => {
beforeEach(() => {
jest.resetModules();
});

// @gate enableBrowserAPI
it('can create browser-only content before the browser renderer is initialized', async () => {
const React = require('react');
const ReactDOM = require('react-dom');
const browserOnly = ReactDOM.browser();
const ReactDOMClient = require('react-dom/client');
const {act} = require('internal-test-utils');

function BrowserOnly() {
React.use(browserOnly);
return <span>Browser</span>;
}

const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(
<React.Suspense fallback={<span>Fallback</span>}>
<BrowserOnly />
</React.Suspense>,
);
});
expect(container.innerHTML).toBe('<span>Browser</span>');
});
});
Loading
Loading