diff --git a/packages/react-dom/src/__tests__/ReactDOMFiber-test.js b/packages/react-dom/src/__tests__/ReactDOMFiber-test.js index ff1ea1771fc..519cda1b49f 100644 --- a/packages/react-dom/src/__tests__/ReactDOMFiber-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMFiber-test.js @@ -41,6 +41,14 @@ describe('ReactDOMFiber', () => { expect(container.textContent).toEqual('10'); }); + it('should render bigints as children', () => { + const Box = ({value}) =>
{value}
; + + ReactDOM.render(, container); + + expect(container.textContent).toEqual('10'); + }); + it('should be called a callback argument', () => { // mounting phase let called = false; diff --git a/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js b/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js index 105fe10eef5..3d164dd9fd0 100644 --- a/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js @@ -2789,6 +2789,17 @@ describe('ReactDOMFizzServer', () => { ); }); + // @gate experimental + it('Supports bigint', async () => { + await act(async () => { + const {pipe} = ReactDOMFizzServer.renderToPipeableStream( +
{10n}
, + ); + pipe(writable); + }); + expect(getVisibleChildren(container)).toEqual(
10
); + }); + describe('bootstrapScriptContent escaping', () => { // @gate experimental it('the "S" in " { diff --git a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationBasic-test.js b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationBasic-test.js index 5baceb1ab7b..7af1cc5b1ab 100644 --- a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationBasic-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationBasic-test.js @@ -74,6 +74,12 @@ describe('ReactDOMServerIntegration', () => { expect(e.nodeValue).toMatch('42'); }); + itRenders('a biging', async render => { + const e = await render(42n); + expect(e.nodeType).toBe(3); + expect(e.nodeValue).toMatch('42'); + }); + itRenders('an array with one child', async render => { const e = await render([
text1
]); const parent = e.parentNode; diff --git a/packages/react-dom/src/__tests__/ReactMultiChildText-test.js b/packages/react-dom/src/__tests__/ReactMultiChildText-test.js index 74c1d7bc310..1e85f314e01 100644 --- a/packages/react-dom/src/__tests__/ReactMultiChildText-test.js +++ b/packages/react-dom/src/__tests__/ReactMultiChildText-test.js @@ -57,6 +57,7 @@ const expectChildren = function(container, children) { continue; } textNode = outerNode.childNodes[mountIndex]; + expect(textNode != null).toBe(true); expect(textNode.nodeType).toBe(3); expect(textNode.data).toBe(child); mountIndex++; @@ -86,6 +87,7 @@ describe('ReactMultiChildText', () => { true, [], 0, '0', 1.2, '1.2', + 10n, '10', '', [], 'foo', 'foo', @@ -96,6 +98,7 @@ describe('ReactMultiChildText', () => { [true], [], [0], ['0'], [1.2], ['1.2'], + [10n], ['10'], [''], [], ['foo'], ['foo'], [
], [
], diff --git a/packages/react-dom/src/__tests__/ReactServerRenderingHydration-test.js b/packages/react-dom/src/__tests__/ReactServerRenderingHydration-test.js index 11a7a510afd..1e247274978 100644 --- a/packages/react-dom/src/__tests__/ReactServerRenderingHydration-test.js +++ b/packages/react-dom/src/__tests__/ReactServerRenderingHydration-test.js @@ -598,4 +598,15 @@ describe('ReactDOMServerHydration', () => { expect(customElement.str).toBe(undefined); expect(customElement.obj).toBe(undefined); }); + + it('should not warn when hydrating bigint', () => { + const domElement = document.createElement('div'); + const reactElement =
{1n}
; + const markup = ReactDOMServer.renderToStaticMarkup(reactElement); + domElement.innerHTML = markup; + + ReactDOM.hydrate(reactElement, domElement); + + expect(domElement.innerHTML).toEqual(markup); + }); }); diff --git a/packages/react-dom/src/client/ReactDOMComponent.js b/packages/react-dom/src/client/ReactDOMComponent.js index 75e017a4518..cb33fe70912 100644 --- a/packages/react-dom/src/client/ReactDOMComponent.js +++ b/packages/react-dom/src/client/ReactDOMComponent.js @@ -317,7 +317,11 @@ function setInitialDOMProperties( if (canSetTextContent) { setTextContent(domElement, nextProp); } - } else if (typeof nextProp === 'number') { + } else if ( + typeof nextProp === 'number' || + // $FlowFixMe - flow is not aware of `bigint` + typeof nextProp === 'bigint' + ) { setTextContent(domElement, '' + nextProp); } } else if ( diff --git a/packages/react-dom/src/client/ReactDOMHostConfig.js b/packages/react-dom/src/client/ReactDOMHostConfig.js index e4973ec667b..86ba5c0d687 100644 --- a/packages/react-dom/src/client/ReactDOMHostConfig.js +++ b/packages/react-dom/src/client/ReactDOMHostConfig.js @@ -254,9 +254,14 @@ export function createInstance( validateDOMNesting(type, null, hostContextDev.ancestorInfo); if ( typeof props.children === 'string' || - typeof props.children === 'number' + typeof props.children === 'number' || + // $FlowFixMe - flow is not aware of `bigint` + typeof props.children === 'bigint' ) { - const string = '' + props.children; + // Flow thinks `children` is still mixed so we need to type-cast + // But now the ESLint plugin thinks `children` is mixed as well. + // eslint-disable-next-line react-internal/safe-string-coercion + const string = '' + ((props.children: any): number | string); const ownAncestorInfo = updatedAncestorInfo( hostContextDev.ancestorInfo, type, diff --git a/packages/react-dom/src/server/ReactPartialRenderer.js b/packages/react-dom/src/server/ReactPartialRenderer.js index 03f5355ef0e..86356c06372 100644 --- a/packages/react-dom/src/server/ReactPartialRenderer.js +++ b/packages/react-dom/src/server/ReactPartialRenderer.js @@ -294,7 +294,12 @@ function getNonChildrenInnerMarkup(props) { } } else { const content = props.children; - if (typeof content === 'string' || typeof content === 'number') { + if ( + typeof content === 'string' || + typeof content === 'number' || + // $FlowFixMe - flow is not aware of `bigint` + typeof content === 'bigint' + ) { return escapeTextForBrowser(content); } } @@ -1001,8 +1006,16 @@ class ReactDOMServerRenderer { context: Object, parentNamespace: string, ): string { - if (typeof child === 'string' || typeof child === 'number') { - const text = '' + child; + if ( + typeof child === 'string' || + typeof child === 'number' || + // $FlowFixMe - flow is not aware of `bigint` + typeof child === 'bigint' + ) { + // Flow thinks `child` is still mixed so we need to type-cast + // But now the ESLint plugin thinks `children` is mixed as well. + // eslint-disable-next-line react-internal/safe-string-coercion + const text = '' + ((child: any): number | string); if (text === '') { return ''; } diff --git a/packages/react-dom/src/server/escapeTextForBrowser.js b/packages/react-dom/src/server/escapeTextForBrowser.js index 4583cef6849..b2195bb4ae9 100644 --- a/packages/react-dom/src/server/escapeTextForBrowser.js +++ b/packages/react-dom/src/server/escapeTextForBrowser.js @@ -104,7 +104,11 @@ function escapeHtml(string) { * @return {string} An escaped string. */ function escapeTextForBrowser(text) { - if (typeof text === 'boolean' || typeof text === 'number') { + if ( + typeof text === 'boolean' || + typeof text === 'number' || + typeof text === 'bigint' + ) { // this shortcircuit helps perf for types that we know will never have // special characters, especially given that this function is used often // for numeric dom ids. diff --git a/packages/react-noop-renderer/src/createReactNoop.js b/packages/react-noop-renderer/src/createReactNoop.js index 13a0f49105c..b392d3b4011 100644 --- a/packages/react-noop-renderer/src/createReactNoop.js +++ b/packages/react-noop-renderer/src/createReactNoop.js @@ -263,7 +263,9 @@ function createReactNoop(reconciler: Function, useMutation: boolean) { throw new Error('Error in host config.'); } return ( - typeof props.children === 'string' || typeof props.children === 'number' + typeof props.children === 'string' || + typeof props.children === 'number' || + typeof props.children === 'bigint' ); } diff --git a/packages/react-reconciler/src/ReactChildFiber.new.js b/packages/react-reconciler/src/ReactChildFiber.new.js index e7eb67328af..9d3f8064cd6 100644 --- a/packages/react-reconciler/src/ReactChildFiber.new.js +++ b/packages/react-reconciler/src/ReactChildFiber.new.js @@ -490,7 +490,9 @@ function ChildReconciler(shouldTrackSideEffects) { ): Fiber | null { if ( (typeof newChild === 'string' && newChild !== '') || - typeof newChild === 'number' + typeof newChild === 'number' || + // $FlowFixMe - flow is not aware of `bigint` + typeof newChild === 'bigint' ) { // Text nodes don't have keys. If the previous node is implicitly keyed // we can continue to replace it without aborting even if it is not a text @@ -567,7 +569,9 @@ function ChildReconciler(shouldTrackSideEffects) { if ( (typeof newChild === 'string' && newChild !== '') || - typeof newChild === 'number' + typeof newChild === 'number' || + // $FlowFixMe - flow is not aware of `bigint` + typeof newChild === 'bigint' ) { // Text nodes don't have keys. If the previous node is implicitly keyed // we can continue to replace it without aborting even if it is not a text @@ -630,7 +634,9 @@ function ChildReconciler(shouldTrackSideEffects) { ): Fiber | null { if ( (typeof newChild === 'string' && newChild !== '') || - typeof newChild === 'number' + typeof newChild === 'number' || + // $FlowFixMe - flow is not aware of `bigint` + typeof newChild === 'bigint' ) { // Text nodes don't have keys, so we neither have to check the old nor // new node for the key. If both are text nodes, they match. @@ -1321,7 +1327,9 @@ function ChildReconciler(shouldTrackSideEffects) { if ( (typeof newChild === 'string' && newChild !== '') || - typeof newChild === 'number' + typeof newChild === 'number' || + // $FlowFixMe - flow is not aware of `bigint` + typeof newChild === 'bigint' ) { return placeSingleChild( reconcileSingleTextNode( diff --git a/packages/react-reconciler/src/ReactChildFiber.old.js b/packages/react-reconciler/src/ReactChildFiber.old.js index 320924d6030..687c8bdc761 100644 --- a/packages/react-reconciler/src/ReactChildFiber.old.js +++ b/packages/react-reconciler/src/ReactChildFiber.old.js @@ -490,7 +490,9 @@ function ChildReconciler(shouldTrackSideEffects) { ): Fiber | null { if ( (typeof newChild === 'string' && newChild !== '') || - typeof newChild === 'number' + typeof newChild === 'number' || + // $FlowFixMe - flow is not aware of `bigint` + typeof newChild === 'bigint' ) { // Text nodes don't have keys. If the previous node is implicitly keyed // we can continue to replace it without aborting even if it is not a text @@ -567,7 +569,9 @@ function ChildReconciler(shouldTrackSideEffects) { if ( (typeof newChild === 'string' && newChild !== '') || - typeof newChild === 'number' + typeof newChild === 'number' || + // $FlowFixMe - flow is not aware of `bigint` + typeof newChild === 'bigint' ) { // Text nodes don't have keys. If the previous node is implicitly keyed // we can continue to replace it without aborting even if it is not a text @@ -630,7 +634,9 @@ function ChildReconciler(shouldTrackSideEffects) { ): Fiber | null { if ( (typeof newChild === 'string' && newChild !== '') || - typeof newChild === 'number' + typeof newChild === 'number' || + // $FlowFixMe - flow is not aware of `bigint` + typeof newChild === 'bigint' ) { // Text nodes don't have keys, so we neither have to check the old nor // new node for the key. If both are text nodes, they match. @@ -1321,7 +1327,9 @@ function ChildReconciler(shouldTrackSideEffects) { if ( (typeof newChild === 'string' && newChild !== '') || - typeof newChild === 'number' + typeof newChild === 'number' || + // $FlowFixMe - flow is not aware of `bigint` + typeof newChild === 'bigint' ) { return placeSingleChild( reconcileSingleTextNode( diff --git a/packages/react-reconciler/src/__tests__/ReactTopLevelText-test.js b/packages/react-reconciler/src/__tests__/ReactTopLevelText-test.js index b1ffd5664c4..cd3d289228a 100644 --- a/packages/react-reconciler/src/__tests__/ReactTopLevelText-test.js +++ b/packages/react-reconciler/src/__tests__/ReactTopLevelText-test.js @@ -37,4 +37,11 @@ describe('ReactTopLevelText', () => { expect(Scheduler).toFlushWithoutYielding(); expect(ReactNoop).toMatchRenderedOutput('10'); }); + + it('should render a component returning bigints directly from render', () => { + const Text = ({value}) => value; + ReactNoop.render(); + expect(Scheduler).toFlushWithoutYielding(); + expect(ReactNoop).toMatchRenderedOutput('10'); + }); }); diff --git a/packages/react-server/src/ReactFizzServer.js b/packages/react-server/src/ReactFizzServer.js index d206a69e48c..cc3bda40bb7 100644 --- a/packages/react-server/src/ReactFizzServer.js +++ b/packages/react-server/src/ReactFizzServer.js @@ -1220,10 +1220,17 @@ function renderNodeDestructive( return; } - if (typeof node === 'number') { + if ( + typeof node === 'number' || + // $FlowFixMe - flow is not aware of `bigint` + typeof node === 'bigint' + ) { pushTextInstance( task.blockedSegment.chunks, - '' + node, + // Flow thinks `children` is still mixed so we need to type-cast + // But now the ESLint plugin thinks `children` is mixed as well. + // eslint-disable-next-line react-internal/safe-string-coercion + '' + ((node: any): number), request.responseState, ); return; diff --git a/packages/react-server/src/ReactFlightServer.js b/packages/react-server/src/ReactFlightServer.js index e08f308a32f..f8eb5b036dc 100644 --- a/packages/react-server/src/ReactFlightServer.js +++ b/packages/react-server/src/ReactFlightServer.js @@ -642,6 +642,8 @@ export function resolveModelToJSON( if ( typeof value === 'boolean' || typeof value === 'number' || + // $FlowFixMe - flow is not aware of `bigint` + typeof value === 'bigint' || typeof value === 'undefined' ) { return value; diff --git a/packages/react/src/ReactChildren.js b/packages/react/src/ReactChildren.js index d0562f53e3b..591413c2372 100644 --- a/packages/react/src/ReactChildren.js +++ b/packages/react/src/ReactChildren.js @@ -96,6 +96,7 @@ function mapIntoArray( switch (type) { case 'string': case 'number': + case 'bigint': invokeCallback = true; break; case 'object': diff --git a/packages/react/src/__tests__/ReactChildren-test.js b/packages/react/src/__tests__/ReactChildren-test.js index f803645ae4d..93977a4b79c 100644 --- a/packages/react/src/__tests__/ReactChildren-test.js +++ b/packages/react/src/__tests__/ReactChildren-test.js @@ -181,11 +181,12 @@ describe('ReactChildren', () => { {false} {null} {undefined} + {9n}
); function assertCalls() { - expect(callback).toHaveBeenCalledTimes(9); + expect(callback).toHaveBeenCalledTimes(10); expect(callback).toHaveBeenCalledWith(div, 0); expect(callback).toHaveBeenCalledWith(span, 1); expect(callback).toHaveBeenCalledWith(a, 2); @@ -195,6 +196,7 @@ describe('ReactChildren', () => { expect(callback).toHaveBeenCalledWith(null, 6); expect(callback).toHaveBeenCalledWith(null, 7); expect(callback).toHaveBeenCalledWith(null, 8); + expect(callback).toHaveBeenCalledWith(9n, 9); callback.mockClear(); } @@ -213,6 +215,7 @@ describe('ReactChildren', () => { , 'string', 1234, + 9n, ]); }); diff --git a/packages/shared/ReactTypes.js b/packages/shared/ReactTypes.js index 17aa509e89e..52c896893d8 100644 --- a/packages/shared/ReactTypes.js +++ b/packages/shared/ReactTypes.js @@ -21,6 +21,7 @@ export type ReactFragment = ReactEmpty | Iterable; export type ReactNodeList = ReactEmpty | React$Node; +// TODO: Add ` | bigint` once its supported by the used version of Flow. export type ReactText = string | number; export type ReactProvider = {