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 "?[Ss]cript" strings are replaced with unicode escaped lowercase s or S depending on case, preserving case sensitivity of nearby characters', async () => {
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 = {