@samitouri / QOS-React-1 / commits / 2f240c91ed

Add support for rendering BigInt (#24580)

Sebastian Silbermann committed Feb 26, 2024 at 19:18 UTC 2f240c91ed54900adee213565cb2039e161629e9
30 files changed +389 -36
packages/react-devtools-shell/src/app/InspectableElements/UnserializableProps.js
+5 -1
@@ -58,5 +58,9 @@ export default function UnserializableProps(): React.Node {
58 }
59
60 function ChildComponent(props: any) {
61 - return null;
61 + return (
62 + <>
63 + <div>{props.bigInt}</div>
64 + </>
65 + );
66 }
packages/react-dom-bindings/src/client/ReactDOMComponent.js
+19 -4
@@ -66,6 +66,7 @@ import {validateProperties as validateUnknownProperties} from '../shared/ReactDO
66 import sanitizeURL from '../shared/sanitizeURL';
67
68 import {
69 + enableBigIntSupport,
70 enableCustomElementPropertySupport,
71 enableClientRenderFallbackOnTextMismatch,
72 enableFormActions,
@@ -326,7 +327,7 @@ function normalizeMarkupForTextOrAttribute(markup: mixed): string {
327
328 export function checkForUnmatchedText(
329 serverText: string,
329 - clientText: string | number,
330 + clientText: string | number | bigint,
331 isConcurrentMode: boolean,
332 shouldWarnDev: boolean,
333 ) {
@@ -397,12 +398,17 @@ function setProp(
398 if (canSetTextContent) {
399 setTextContent(domElement, value);
400 }
400 - } else if (typeof value === 'number') {
401 + } else if (
402 + typeof value === 'number' ||
403 + (enableBigIntSupport && typeof value === 'bigint')
404 + ) {
405 if (__DEV__) {
406 + // $FlowFixMe[unsafe-addition] Flow doesn't want us to use `+` operator with string and bigint
407 validateTextNesting('' + value, tag);
408 }
409 const canSetTextContent = tag !== 'body';
410 if (canSetTextContent) {
411 + // $FlowFixMe[unsafe-addition] Flow doesn't want us to use `+` operator with string and bigint
412 setTextContent(domElement, '' + value);
413 }
414 }
@@ -955,7 +961,11 @@ function setPropOnCustomElement(
961 case 'children': {
962 if (typeof value === 'string') {
963 setTextContent(domElement, value);
958 - } else if (typeof value === 'number') {
964 + } else if (
965 + typeof value === 'number' ||
966 + (enableBigIntSupport && typeof value === 'bigint')
967 + ) {
968 + // $FlowFixMe[unsafe-addition] Flow doesn't want us to use `+` operator with string and bigint
969 setTextContent(domElement, '' + value);
970 }
971 break;
@@ -2817,7 +2827,12 @@ export function diffHydratedProperties(
2827 // even listeners these nodes might be wired up to.
2828 // TODO: Warn if there is more than a single textNode as a child.
2829 // TODO: Should we use domElement.firstChild.nodeValue to compare?
2820 - if (typeof children === 'string' || typeof children === 'number') {
2830 + if (
2831 + typeof children === 'string' ||
2832 + typeof children === 'number' ||
2833 + (enableBigIntSupport && typeof children === 'bigint')
2834 + ) {
2835 + // $FlowFixMe[unsafe-addition] Flow doesn't want us to use `+` operator with string and bigint
2836 if (domElement.textContent !== '' + children) {
2837 if (props.suppressHydrationWarning !== true) {
2838 checkForUnmatchedText(
packages/react-dom-bindings/src/client/ReactDOMOption.js
+6 -1
@@ -8,6 +8,7 @@
8 */
9
10 import {Children} from 'react';
11 +import {enableBigIntSupport} from 'shared/ReactFeatureFlags';
12
13 let didWarnSelectedSetOnOption = false;
14 let didWarnInvalidChild = false;
@@ -26,7 +27,11 @@ export function validateOptionProps(element: Element, props: Object) {
27 if (child == null) {
28 return;
29 }
29 - if (typeof child === 'string' || typeof child === 'number') {
30 + if (
31 + typeof child === 'string' ||
32 + typeof child === 'number' ||
33 + (enableBigIntSupport && typeof child === 'bigint')
34 + ) {
35 return;
36 }
37 if (!didWarnInvalidChild) {
packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js
+2
@@ -89,6 +89,7 @@ import {
89 import {retryIfBlockedOn} from '../events/ReactDOMEventReplaying';
90
91 import {
92 + enableBigIntSupport,
93 enableCreateEventHandleAPI,
94 enableScopeAPI,
95 enableFloat,
@@ -548,6 +549,7 @@ export function shouldSetTextContent(type: string, props: Props): boolean {
549 type === 'noscript' ||
550 typeof props.children === 'string' ||
551 typeof props.children === 'number' ||
552 + (enableBigIntSupport && typeof props.children === 'bigint') ||
553 (typeof props.dangerouslySetInnerHTML === 'object' &&
554 props.dangerouslySetInnerHTML !== null &&
555 props.dangerouslySetInnerHTML.__html != null)
packages/react-dom-bindings/src/client/ToStringValue.js
+8
@@ -8,10 +8,12 @@
8 */
9
10 import {checkFormFieldValueStringCoercion} from 'shared/CheckStringCoercion';
11 +import {enableBigIntSupport} from 'shared/ReactFeatureFlags';
12
13 export opaque type ToStringValue =
14 | boolean
15 | number
16 + | bigint
17 | Object
18 | string
19 | null
@@ -28,6 +30,12 @@ export function toString(value: ToStringValue): string {
30
31 export function getToStringValue(value: mixed): ToStringValue {
32 switch (typeof value) {
33 + case 'bigint':
34 + if (!enableBigIntSupport) {
35 + // bigint is assigned as empty string
36 + return '';
37 + }
38 + // fallthrough for BigInt support
39 case 'boolean':
40 case 'number':
41 case 'string':
packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js
+17 -7
@@ -28,6 +28,7 @@ import {
28 import {Children} from 'react';
29
30 import {
31 + enableBigIntSupport,
32 enableFilterEmptyStringAttributesDOM,
33 enableCustomElementPropertySupport,
34 enableFloat,
@@ -1626,7 +1627,9 @@ function flattenOptionChildren(children: mixed): string {
1627 if (
1628 !didWarnInvalidOptionChildren &&
1629 typeof child !== 'string' &&
1629 - typeof child !== 'number'
1630 + typeof child !== 'number' &&
1631 + ((enableBigIntSupport && typeof child !== 'bigint') ||
1632 + !enableBigIntSupport)
1633 ) {
1634 didWarnInvalidOptionChildren = true;
1635 console.error(
@@ -2960,36 +2963,40 @@ function pushTitle(
2963
2964 if (Array.isArray(children) && children.length > 1) {
2965 console.error(
2963 - 'React expects the `children` prop of <title> tags to be a string, number, or object with a novel `toString` method but found an Array with length %s instead.' +
2966 + 'React expects the `children` prop of <title> tags to be a string, number%s, or object with a novel `toString` method but found an Array with length %s instead.' +
2967 ' Browsers treat all child Nodes of <title> tags as Text content and React expects to be able to convert `children` of <title> tags to a single string value' +
2968 ' which is why Arrays of length greater than 1 are not supported. When using JSX it can be commong to combine text nodes and value nodes.' +
2969 ' For example: <title>hello {nameOfUser}</title>. While not immediately apparent, `children` in this case is an Array with length 2. If your `children` prop' +
2970 ' is using this form try rewriting it using a template string: <title>{`hello ${nameOfUser}`}</title>.',
2971 + enableBigIntSupport ? ', bigint' : '',
2972 children.length,
2973 );
2974 } else if (typeof child === 'function' || typeof child === 'symbol') {
2975 const childType =
2976 typeof child === 'function' ? 'a Function' : 'a Sybmol';
2977 console.error(
2974 - 'React expect children of <title> tags to be a string, number, or object with a novel `toString` method but found %s instead.' +
2978 + 'React expect children of <title> tags to be a string, number%s, or object with a novel `toString` method but found %s instead.' +
2979 ' Browsers treat all child Nodes of <title> tags as Text content and React expects to be able to convert children of <title>' +
2980 ' tags to a single string value.',
2981 + enableBigIntSupport ? ', bigint' : '',
2982 childType,
2983 );
2984 } else if (child && child.toString === {}.toString) {
2985 if (child.$$typeof != null) {
2986 console.error(
2982 - 'React expects the `children` prop of <title> tags to be a string, number, or object with a novel `toString` method but found an object that appears to be' +
2987 + 'React expects the `children` prop of <title> tags to be a string, number%s, or object with a novel `toString` method but found an object that appears to be' +
2988 ' a React element which never implements a suitable `toString` method. Browsers treat all child Nodes of <title> tags as Text content and React expects to' +
2989 ' be able to convert children of <title> tags to a single string value which is why rendering React elements is not supported. If the `children` of <title> is' +
2990 ' a React Component try moving the <title> tag into that component. If the `children` of <title> is some HTML markup change it to be Text only to be valid HTML.',
2991 + enableBigIntSupport ? ', bigint' : '',
2992 );
2993 } else {
2994 console.error(
2989 - 'React expects the `children` prop of <title> tags to be a string, number, or object with a novel `toString` method but found an object that does not implement' +
2995 + 'React expects the `children` prop of <title> tags to be a string, number%s, or object with a novel `toString` method but found an object that does not implement' +
2996 ' a suitable `toString` method. Browsers treat all child Nodes of <title> tags as Text content and React expects to be able to convert children of <title> tags' +
2997 ' to a single string value. Using the default `toString` method available on every object is almost certainly an error. Consider whether the `children` of this <title>' +
2998 ' is an object in error and change it to a string or number value if so. Otherwise implement a `toString` method that React can use to produce a valid <title>.',
2999 + enableBigIntSupport ? ', bigint' : '',
3000 );
3001 }
3002 }
@@ -3123,14 +3130,17 @@ function pushStartTitle(
3130 } else if (
3131 childForValidation != null &&
3132 typeof childForValidation !== 'string' &&
3126 - typeof childForValidation !== 'number'
3133 + typeof childForValidation !== 'number' &&
3134 + ((enableBigIntSupport && typeof childForValidation !== 'bigint') ||
3135 + !enableBigIntSupport)
3136 ) {
3137 console.error(
3129 - 'A title element received a value that was not a string or number for children. ' +
3138 + 'A title element received a value that was not a string or number%s for children. ' +
3139 'In the browser title Elements can only have Text Nodes as children. If ' +
3140 'the children being rendered output more than a single text node in aggregate the browser ' +
3141 'will display markup and comments as text in the title and hydration will likely fail and ' +
3142 'fall back to client rendering',
3143 + enableBigIntSupport ? ' or bigint' : '',
3144 );
3145 }
3146 }
packages/react-dom-bindings/src/server/escapeTextForBrowser.js
+6 -1
@@ -39,6 +39,7 @@
39 */
40
41 import {checkHtmlStringCoercion} from 'shared/CheckStringCoercion';
42 +import {enableBigIntSupport} from 'shared/ReactFeatureFlags';
43
44 const matchHtmlRegExp = /["'&<>]/;
45
@@ -106,7 +107,11 @@ function escapeHtml(string: string) {
107 * @return {string} An escaped string.
108 */
109 function escapeTextForBrowser(text: string | number | boolean): string {
109 - if (typeof text === 'boolean' || typeof text === 'number') {
110 + if (
111 + typeof text === 'boolean' ||
112 + typeof text === 'number' ||
113 + (enableBigIntSupport && typeof text === 'bigint')
114 + ) {
115 // this shortcircuit helps perf for types that we know will never have
116 // special characters, especially given that this function is used often
117 // for numeric dom ids.
packages/react-dom/src/__tests__/ReactDOMFiber-test.js
+11
@@ -60,6 +60,17 @@ describe('ReactDOMFiber', () => {
60 expect(container.textContent).toEqual('10');
61 });
62
63 + // @gate enableBigIntSupport
64 + it('should render bigints as children', async () => {
65 + const Box = ({value}) => <div>{value}</div>;
66 +
67 + await act(async () => {
68 + root.render(<Box value={10n} />);
69 + });
70 +
71 + expect(container.textContent).toEqual('10');
72 + });
73 +
74 it('should call an effect after mount/update (replacing render callback pattern)', async () => {
75 function Component() {
76 React.useEffect(() => {
packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
+129 -2
@@ -3373,6 +3373,17 @@ describe('ReactDOMFizzServer', () => {
3373 );
3374 });
3375
3376 + // @gate enableBigIntSupport
3377 + it('Supports bigint', async () => {
3378 + await act(async () => {
3379 + const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
3380 + <div>{10n}</div>,
3381 + );
3382 + pipe(writable);
3383 + });
3384 + expect(getVisibleChildren(container)).toEqual(<div>10</div>);
3385 + });
3386 +
3387 it('Supports custom abort reasons with a string', async () => {
3388 function App() {
3389 return (
@@ -5642,6 +5653,60 @@ describe('ReactDOMFizzServer', () => {
5653 expect(getVisibleChildren(document.head)).toEqual(<title>hello</title>);
5654 });
5655
5656 + it('should accept a single number child', async () => {
5657 + // a Single number child
5658 + function App() {
5659 + return (
5660 + <head>
5661 + <title>4</title>
5662 + </head>
5663 + );
5664 + }
5665 +
5666 + await act(() => {
5667 + const {pipe} = renderToPipeableStream(<App />);
5668 + pipe(writable);
5669 + });
5670 + expect(getVisibleChildren(document.head)).toEqual(<title>4</title>);
5671 +
5672 + const errors = [];
5673 + ReactDOMClient.hydrateRoot(container, <App />, {
5674 + onRecoverableError(error) {
5675 + errors.push(error.message);
5676 + },
5677 + });
5678 + await waitForAll([]);
5679 + expect(errors).toEqual([]);
5680 + expect(getVisibleChildren(document.head)).toEqual(<title>4</title>);
5681 + });
5682 +
5683 + it('should accept a single bigint child', async () => {
5684 + // a Single number child
5685 + function App() {
5686 + return (
5687 + <head>
5688 + <title>5n</title>
5689 + </head>
5690 + );
5691 + }
5692 +
5693 + await act(() => {
5694 + const {pipe} = renderToPipeableStream(<App />);
5695 + pipe(writable);
5696 + });
5697 + expect(getVisibleChildren(document.head)).toEqual(<title>5n</title>);
5698 +
5699 + const errors = [];
5700 + ReactDOMClient.hydrateRoot(container, <App />, {
5701 + onRecoverableError(error) {
5702 + errors.push(error.message);
5703 + },
5704 + });
5705 + await waitForAll([]);
5706 + expect(errors).toEqual([]);
5707 + expect(getVisibleChildren(document.head)).toEqual(<title>5n</title>);
5708 + });
5709 +
5710 it('should accept children array of length 1 containing a string', async () => {
5711 // a Single string child
5712 function App() {
@@ -5684,7 +5749,9 @@ describe('ReactDOMFizzServer', () => {
5749 pipe(writable);
5750 });
5751 }).toErrorDev([
5687 - 'React expects the `children` prop of <title> tags to be a string, number, or object with a novel `toString` method but found an Array with length 2 instead. Browsers treat all child Nodes of <title> tags as Text content and React expects to be able to convert `children` of <title> tags to a single string value which is why Arrays of length greater than 1 are not supported. When using JSX it can be commong to combine text nodes and value nodes. For example: <title>hello {nameOfUser}</title>. While not immediately apparent, `children` in this case is an Array with length 2. If your `children` prop is using this form try rewriting it using a template string: <title>{`hello ${nameOfUser}`}</title>.',
5752 + 'React expects the `children` prop of <title> tags to be a string, number' +
5753 + gate(flags => (flags.enableBigIntSupport ? ', bigint' : '')) +
5754 + ', or object with a novel `toString` method but found an Array with length 2 instead. Browsers treat all child Nodes of <title> tags as Text content and React expects to be able to convert `children` of <title> tags to a single string value which is why Arrays of length greater than 1 are not supported. When using JSX it can be commong to combine text nodes and value nodes. For example: <title>hello {nameOfUser}</title>. While not immediately apparent, `children` in this case is an Array with length 2. If your `children` prop is using this form try rewriting it using a template string: <title>{`hello ${nameOfUser}`}</title>.',
5755 ]);
5756
5757 if (gate(flags => flags.enableFloat)) {
@@ -5744,7 +5811,67 @@ describe('ReactDOMFizzServer', () => {
5811 pipe(writable);
5812 });
5813 }).toErrorDev([
5747 - 'React expects the `children` prop of <title> tags to be a string, number, or object with a novel `toString` method but found an object that appears to be a React element which never implements a suitable `toString` method. Browsers treat all child Nodes of <title> tags as Text content and React expects to be able to convert children of <title> tags to a single string value which is why rendering React elements is not supported. If the `children` of <title> is a React Component try moving the <title> tag into that component. If the `children` of <title> is some HTML markup change it to be Text only to be valid HTML.',
5814 + 'React expects the `children` prop of <title> tags to be a string, number' +
5815 + gate(flags => (flags.enableBigIntSupport ? ', bigint' : '')) +
5816 + ', or object with a novel `toString` method but found an object that appears to be a React element which never implements a suitable `toString` method. Browsers treat all child Nodes of <title> tags as Text content and React expects to be able to convert children of <title> tags to a single string value which is why rendering React elements is not supported. If the `children` of <title> is a React Component try moving the <title> tag into that component. If the `children` of <title> is some HTML markup change it to be Text only to be valid HTML.',
5817 + ]);
5818 + } else {
5819 + await expect(async () => {
5820 + await act(() => {
5821 + const {pipe} = renderToPipeableStream(<App />);
5822 + pipe(writable);
5823 + });
5824 + }).toErrorDev([
5825 + 'A title element received a React element for children. In the browser title Elements can only have Text Nodes as children. If the children being rendered output more than a single text node in aggregate the browser will display markup and comments as text in the title and hydration will likely fail and fall back to client rendering',
5826 + ]);
5827 + }
5828 +
5829 + if (gate(flags => flags.enableFloat)) {
5830 + // object titles are toStringed when float is on
5831 + expect(getVisibleChildren(document.head)).toEqual(
5832 + <title>{'[object Object]'}</title>,
5833 + );
5834 + } else {
5835 + expect(getVisibleChildren(document.head)).toEqual(<title>hello</title>);
5836 + }
5837 +
5838 + const errors = [];
5839 + ReactDOMClient.hydrateRoot(document.head, <App />, {
5840 + onRecoverableError(error) {
5841 + errors.push(error.message);
5842 + },
5843 + });
5844 + await waitForAll([]);
5845 + expect(errors).toEqual([]);
5846 + if (gate(flags => flags.enableFloat)) {
5847 + // object titles are toStringed when float is on
5848 + expect(getVisibleChildren(document.head)).toEqual(
5849 + <title>{'[object Object]'}</title>,
5850 + );
5851 + } else {
5852 + expect(getVisibleChildren(document.head)).toEqual(<title>hello</title>);
5853 + }
5854 + });
5855 +
5856 + it('should warn in dev if you pass an object that does not implement toString as a child to <title>', async () => {
5857 + function App() {
5858 + return (
5859 + <head>
5860 + <title>{{}}</title>
5861 + </head>
5862 + );
5863 + }
5864 +
5865 + if (gate(flags => flags.enableFloat)) {
5866 + await expect(async () => {
5867 + await act(() => {
5868 + const {pipe} = renderToPipeableStream(<App />);
5869 + pipe(writable);
5870 + });
5871 + }).toErrorDev([
5872 + 'React expects the `children` prop of <title> tags to be a string, number' +
5873 + gate(flags => (flags.enableBigIntSupport ? ', bigint' : '')) +
5874 + ', or object with a novel `toString` method but found an object that does not implement a suitable `toString` method. Browsers treat all child Nodes of <title> tags as Text content and React expects to be able to convert children of <title> tags to a single string value. Using the default `toString` method available on every object is almost certainly an error. Consider whether the `children` of this <title> is an object in error and change it to a string or number value if so. Otherwise implement a `toString` method that React can use to produce a valid <title>.',
5875 ]);
5876 } else {
5877 await expect(async () => {
packages/react-dom/src/__tests__/ReactDOMInput-test.js
+29
@@ -657,6 +657,16 @@ describe('ReactDOMInput', () => {
657 expect(div.firstChild.getAttribute('defaultValue')).toBe(null);
658 });
659
660 + it('should render bigint defaultValue for SSR', () => {
661 + const markup = ReactDOMServer.renderToString(
662 + <input type="text" defaultValue={5n} />,
663 + );
664 + const div = document.createElement('div');
665 + div.innerHTML = markup;
666 + expect(div.firstChild.getAttribute('value')).toBe('5');
667 + expect(div.firstChild.getAttribute('defaultValue')).toBe(null);
668 + });
669 +
670 it('should render value for SSR', () => {
671 const element = <input type="text" value="1" onChange={() => {}} />;
672 const markup = ReactDOMServer.renderToString(element);
@@ -666,6 +676,15 @@ describe('ReactDOMInput', () => {
676 expect(div.firstChild.getAttribute('defaultValue')).toBe(null);
677 });
678
679 + it('should render bigint value for SSR', () => {
680 + const element = <input type="text" value={5n} onChange={() => {}} />;
681 + const markup = ReactDOMServer.renderToString(element);
682 + const div = document.createElement('div');
683 + div.innerHTML = markup;
684 + expect(div.firstChild.getAttribute('value')).toBe('5');
685 + expect(div.firstChild.getAttribute('defaultValue')).toBe(null);
686 + });
687 +
688 it('should render name attribute if it is supplied', async () => {
689 await act(() => {
690 root.render(<input type="text" name="name" />);
@@ -830,6 +849,16 @@ describe('ReactDOMInput', () => {
849 expect(node.value).toBe('0');
850 });
851
852 + // @gate enableBigIntSupport
853 + it('should display `value` of bigint 5', async () => {
854 + await act(() => {
855 + root.render(<input type="text" value={5n} onChange={emptyFunction} />);
856 + });
857 + const node = container.firstChild;
858 +
859 + expect(node.value).toBe('5');
860 + });
861 +
862 it('should allow setting `value` to `true`', async () => {
863 await act(() => {
864 root.render(<input type="text" value="yolo" onChange={emptyFunction} />);
packages/react-dom/src/__tests__/ReactDOMOption-test.js
+7
@@ -172,6 +172,13 @@ describe('ReactDOMOption', () => {
172 expect(node.value).toBe('hello');
173 });
174
175 + // @gate enableBigIntSupport
176 + it('should support bigint values', () => {
177 + const node = ReactTestUtils.renderIntoDocument(<option>{5n}</option>);
178 + expect(node.innerHTML).toBe('5');
179 + expect(node.value).toBe('5');
180 + });
181 +
182 it('should be able to use dangerouslySetInnerHTML on option', () => {
183 const stub = <option dangerouslySetInnerHTML={{__html: 'foobar'}} />;
184 let node;
packages/react-dom/src/__tests__/ReactDOMServerIntegrationBasic-test.js
+10
@@ -72,6 +72,16 @@ describe('ReactDOMServerIntegration', () => {
72 expect(e.nodeValue).toMatch('42');
73 });
74
75 + itRenders('a bigint', async render => {
76 + const e = await render(42n);
77 + if (gate(flags => flags.enableBigIntSupport)) {
78 + expect(e.nodeType).toBe(3);
79 + expect(e.nodeValue).toMatch('42');
80 + } else {
81 + expect(e).toBe(null);
82 + }
83 + });
84 +
85 itRenders('an array with one child', async render => {
86 const e = await render([<div key={1}>text1</div>]);
87 const parent = e.parentNode;
packages/react-dom/src/__tests__/ReactDOMServerIntegrationInput-test.js
+14 -1
@@ -32,7 +32,8 @@ function initModules() {
32 };
33 }
34
35 -const {resetModules, itRenders} = ReactDOMServerIntegrationUtils(initModules);
35 +const {resetModules, itRenders, serverRender, streamRender} =
36 + ReactDOMServerIntegrationUtils(initModules);
37
38 // TODO: Run this in React Fire mode after we figure out the SSR behavior.
39 const desc = disableInputAttributeSyncing ? xdescribe : describe;
@@ -46,6 +47,18 @@ desc('ReactDOMServerIntegrationInput', () => {
47 expect(e.value).toBe('foo');
48 });
49
50 + itRenders('an input with a bigint value and an onChange', async render => {
51 + console.log(gate(flags => flags.enableBigIntSupport));
52 + const e = await render(<input value={5n} onChange={() => {}} />);
53 + expect(e.value).toBe(
54 + gate(flags => flags.enableBigIntSupport) ||
55 + render === serverRender ||
56 + render === streamRender
57 + ? '5'
58 + : '',
59 + );
60 + });
61 +
62 itRenders('an input with a value and readOnly', async render => {
63 const e = await render(<input value="foo" readOnly={true} />);
64 expect(e.value).toBe('foo');
packages/react-dom/src/__tests__/ReactDOMServerIntegrationSelect-test.js
+6 -2
@@ -218,11 +218,15 @@ describe('ReactDOMServerIntegrationSelect', () => {
218 itRenders('a select option with flattened children', async render => {
219 const e = await render(
220 <select value="bar" readOnly={true}>
221 - <option value="bar">A {'B'}</option>
221 + <option value="bar">
222 + A {'B'} {5n}
223 + </option>
224 </select>,
225 );
226 const option = e.options[0];
225 - expect(option.textContent).toBe('A B');
227 + expect(option.textContent).toBe(
228 + gate(flags => flags.enableBigIntSupport) ? 'A B 5' : 'A B ',
229 + );
230 expect(option.value).toBe('bar');
231 expect(option.selected).toBe(true);
232 });
packages/react-dom/src/__tests__/ReactDOMServerIntegrationTextarea-test.js
+14 -2
@@ -30,7 +30,8 @@ function initModules() {
30 };
31 }
32
33 -const {resetModules, itRenders} = ReactDOMServerIntegrationUtils(initModules);
33 +const {resetModules, itRenders, serverRender, streamRender} =
34 + ReactDOMServerIntegrationUtils(initModules);
35
36 describe('ReactDOMServerIntegrationTextarea', () => {
37 beforeEach(() => {
@@ -47,12 +48,23 @@ describe('ReactDOMServerIntegrationTextarea', () => {
48 expect(e.value).toBe('foo');
49 });
50
51 + itRenders('a textarea with a bigint value and an onChange', async render => {
52 + const e = await render(<textarea value={5n} onChange={() => {}} />);
53 + expect(e.getAttribute('value')).toBe(null);
54 + expect(e.value).toBe(
55 + gate(flags => flags.enableBigIntSupport) ||
56 + render === serverRender ||
57 + render === streamRender
58 + ? '5'
59 + : '',
60 + );
61 + });
62 +
63 itRenders('a textarea with a value of undefined', async render => {
64 const e = await render(<textarea value={undefined} />);
65 expect(e.getAttribute('value')).toBe(null);
66 expect(e.value).toBe('');
67 });
55 -
68 itRenders('a textarea with a value and readOnly', async render => {
69 const e = await render(<textarea value="foo" readOnly={true} />);
70 // textarea DOM elements don't have a value **attribute**, the text is
packages/react-dom/src/__tests__/ReactDOMTextarea-test.js
+13
@@ -80,6 +80,19 @@ describe('ReactDOMTextarea', () => {
80 expect(node.value).toBe('0');
81 });
82
83 + // @gate enableBigIntSupport
84 + it('should display `defaultValue` of bigint 0', async () => {
85 + const container = document.createElement('div');
86 + const root = ReactDOMClient.createRoot(container);
87 + const node = await renderTextarea(
88 + <textarea defaultValue={0n} />,
89 + container,
90 + root,
91 + );
92 +
93 + expect(node.value).toBe('0');
94 + });
95 +
96 it('should display "false" for `defaultValue` of `false`', async () => {
97 const container = document.createElement('div');
98 const root = ReactDOMClient.createRoot(container);
packages/react-dom/src/__tests__/ReactMultiChildText-test.js
+10
@@ -58,6 +58,7 @@ const expectChildren = function (container, children) {
58 continue;
59 }
60 textNode = outerNode.childNodes[mountIndex];
61 + expect(textNode != null).toBe(true);
62 expect(textNode.nodeType).toBe(3);
63 expect(textNode.data).toBe(child);
64 mountIndex++;
@@ -173,6 +174,15 @@ describe('ReactMultiChildText', () => {
174 ]);
175 });
176
177 + // @gate enableBigIntSupport
178 + it('should correctly handle bigint children for render and update', async () => {
179 + // prettier-ignore
180 + await testAllPermutations([
181 + 10n, '10',
182 + [10n], ['10']
183 + ]);
184 + });
185 +
186 it('should throw if rendering both HTML and children', async () => {
187 const container = document.createElement('div');
188 const root = ReactDOMClient.createRoot(container);
packages/react-noop-renderer/src/createReactNoop.js
+11 -2
@@ -272,7 +272,9 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
272 throw new Error('Error in host config.');
273 }
274 return (
275 - typeof props.children === 'string' || typeof props.children === 'number'
275 + typeof props.children === 'string' ||
276 + typeof props.children === 'number' ||
277 + typeof props.children === 'bigint'
278 );
279 }
280
@@ -828,7 +830,14 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
830 return childToJSX(child[0], null);
831 }
832 const children = child.map(c => childToJSX(c, null));
831 - if (children.every(c => typeof c === 'string' || typeof c === 'number')) {
833 + if (
834 + children.every(
835 + c =>
836 + typeof c === 'string' ||
837 + typeof c === 'number' ||
838 + typeof c === 'bigint',
839 + )
840 + ) {
841 return children.join('');
842 }
843 return children;
packages/react-reconciler/src/ReactChildFiber.js
+13 -4
@@ -25,6 +25,7 @@ import {
25 Forked,
26 PlacementDEV,
27 } from './ReactFiberFlags';
28 +import {enableBigIntSupport} from 'shared/ReactFeatureFlags';
29 import {
30 getIteratorFn,
31 REACT_ELEMENT_TYPE,
@@ -665,12 +666,14 @@ function createChildReconciler(
666 ): Fiber | null {
667 if (
668 (typeof newChild === 'string' && newChild !== '') ||
668 - typeof newChild === 'number'
669 + typeof newChild === 'number' ||
670 + (enableBigIntSupport && typeof newChild === 'bigint')
671 ) {
672 // Text nodes don't have keys. If the previous node is implicitly keyed
673 // we can continue to replace it without aborting even if it is not a text
674 // node.
675 const created = createFiberFromText(
676 + // $FlowFixMe[unsafe-addition] Flow doesn't want us to use `+` operator with string and bigint
677 '' + newChild,
678 returnFiber.mode,
679 lanes,
@@ -785,7 +788,8 @@ function createChildReconciler(
788
789 if (
790 (typeof newChild === 'string' && newChild !== '') ||
788 - typeof newChild === 'number'
791 + typeof newChild === 'number' ||
792 + (enableBigIntSupport && typeof newChild === 'bigint')
793 ) {
794 // Text nodes don't have keys. If the previous node is implicitly keyed
795 // we can continue to replace it without aborting even if it is not a text
@@ -796,6 +800,7 @@ function createChildReconciler(
800 return updateTextNode(
801 returnFiber,
802 oldFiber,
803 + // $FlowFixMe[unsafe-addition] Flow doesn't want us to use `+` operator with string and bigint
804 '' + newChild,
805 lanes,
806 debugInfo,
@@ -908,7 +913,8 @@ function createChildReconciler(
913 ): Fiber | null {
914 if (
915 (typeof newChild === 'string' && newChild !== '') ||
911 - typeof newChild === 'number'
916 + typeof newChild === 'number' ||
917 + (enableBigIntSupport && typeof newChild === 'bigint')
918 ) {
919 // Text nodes don't have keys, so we neither have to check the old nor
920 // new node for the key. If both are text nodes, they match.
@@ -916,6 +922,7 @@ function createChildReconciler(
922 return updateTextNode(
923 returnFiber,
924 matchedFiber,
925 + // $FlowFixMe[unsafe-addition] Flow doesn't want us to use `+` operator with string and bigint
926 '' + newChild,
927 lanes,
928 debugInfo,
@@ -1723,12 +1730,14 @@ function createChildReconciler(
1730
1731 if (
1732 (typeof newChild === 'string' && newChild !== '') ||
1726 - typeof newChild === 'number'
1733 + typeof newChild === 'number' ||
1734 + (enableBigIntSupport && typeof newChild === 'bigint')
1735 ) {
1736 return placeSingleChild(
1737 reconcileSingleTextNode(
1738 returnFiber,
1739 currentFirstChild,
1740 + // $FlowFixMe[unsafe-addition] Flow doesn't want us to use `+` operator with string and bigint
1741 '' + newChild,
1742 lanes,
1743 ),
packages/react-reconciler/src/__tests__/ReactTopLevelText-test.js
+8
@@ -39,4 +39,12 @@ describe('ReactTopLevelText', () => {
39 await waitForAll([]);
40 expect(ReactNoop).toMatchRenderedOutput('10');
41 });
42 +
43 + // @gate enableBigIntSupport
44 + it('should render a component returning bigints directly from render', async () => {
45 + const Text = ({value}) => value;
46 + ReactNoop.render(<Text value={10n} />);
47 + await waitForAll([]);
48 + expect(ReactNoop).toMatchRenderedOutput('10');
49 + });
50 });
packages/react-server/src/ReactFizzServer.js
+5 -1
@@ -139,6 +139,7 @@ import ReactSharedInternals from 'shared/ReactSharedInternals';
139 import {
140 disableLegacyContext,
141 disableModulePatternComponents,
142 + enableBigIntSupport,
143 enableScopeAPI,
144 enableSuspenseAvoidThisFallbackFizz,
145 enableFloat,
@@ -2364,7 +2365,10 @@ function renderNodeDestructive(
2365 return;
2366 }
2367
2367 - if (typeof node === 'number') {
2368 + if (
2369 + typeof node === 'number' ||
2370 + (enableBigIntSupport && typeof node === 'bigint')
2371 + ) {
2372 const segment = task.blockedSegment;
2373 if (segment === null) {
2374 // We assume a text node doesn't have a representation in the replay set,
packages/react/src/ReactChildren.js
+6
@@ -16,6 +16,7 @@ import type {
16 } from 'shared/ReactTypes';
17
18 import isArray from 'shared/isArray';
19 +import {enableBigIntSupport} from 'shared/ReactFeatureFlags';
20 import {
21 getIteratorFn,
22 REACT_ELEMENT_TYPE,
@@ -163,6 +164,11 @@ function mapIntoArray(
164 invokeCallback = true;
165 } else {
166 switch (type) {
167 + case 'bigint':
168 + if (!enableBigIntSupport) {
169 + break;
170 + }
171 + // fallthrough for enabled BigInt support
172 case 'string':
173 case 'number':
174 invokeCallback = true;
packages/react/src/__tests__/ReactChildren-test.js
+27 -8
@@ -183,11 +183,14 @@ describe('ReactChildren', () => {
183 {false}
184 {null}
185 {undefined}
186 + {9n}
187 </div>
188 );
189
190 function assertCalls() {
190 - expect(callback).toHaveBeenCalledTimes(9);
191 + expect(callback).toHaveBeenCalledTimes(
192 + gate(flags => flags.enableBigIntSupport) ? 10 : 9,
193 + );
194 expect(callback).toHaveBeenCalledWith(div, 0);
195 expect(callback).toHaveBeenCalledWith(span, 1);
196 expect(callback).toHaveBeenCalledWith(a, 2);
@@ -197,6 +200,11 @@ describe('ReactChildren', () => {
200 expect(callback).toHaveBeenCalledWith(null, 6);
201 expect(callback).toHaveBeenCalledWith(null, 7);
202 expect(callback).toHaveBeenCalledWith(null, 8);
203 + if (gate(flags => flags.enableBigIntSupport)) {
204 + expect(callback).toHaveBeenCalledWith(9n, 9);
205 + } else {
206 + expect(callback).not.toHaveBeenCalledWith(9n, 9);
207 + }
208 callback.mockClear();
209 }
210
@@ -209,13 +217,24 @@ describe('ReactChildren', () => {
217 context,
218 );
219 assertCalls();
212 - expect(mappedChildren).toEqual([
213 - <div key=".$divNode" />,
214 - <span key=".1:0:$spanNode" />,
215 - <a key=".2:$aNode" />,
216 - 'string',
217 - 1234,
218 - ]);
220 + expect(mappedChildren).toEqual(
221 + gate(flags => flags.enableBigIntSupport)
222 + ? [
223 + <div key=".$divNode" />,
224 + <span key=".1:0:$spanNode" />,
225 + <a key=".2:$aNode" />,
226 + 'string',
227 + 1234,
228 + 9n,
229 + ]
230 + : [
231 + <div key=".$divNode" />,
232 + <span key=".1:0:$spanNode" />,
233 + <a key=".2:$aNode" />,
234 + 'string',
235 + 1234,
236 + ],
237 + );
238 });
239
240 it('should be called for each child in nested structure', () => {
packages/shared/ReactFeatureFlags.js
+2
@@ -174,6 +174,8 @@ export const disableClientCache = false;
174 // Changes Server Components Reconciliation when they have keys
175 export const enableServerComponentKeys = __NEXT_MAJOR__;
176
177 +export const enableBigIntSupport = __NEXT_MAJOR__;
178 +
179 /**
180 * Enables a new error detection for infinite render loops from updates caused
181 * by setState or similar outside of the component owning the state.
packages/shared/forks/ReactFeatureFlags.native-fb.js
+2
@@ -104,5 +104,7 @@ export const enableRefAsProp = false;
104
105 export const enableReactTestRendererWarning = false;
106
107 +export const enableBigIntSupport = false;
108 +
109 // Flow magic to verify the exports of this file match the original version.
110 ((((null: any): ExportsType): FeatureFlagsType): ExportsType);
packages/shared/forks/ReactFeatureFlags.native-oss.js
+2
@@ -95,5 +95,7 @@ export const enableRefAsProp = false;
95
96 export const enableReactTestRendererWarning = false;
97
98 +export const enableBigIntSupport = false;
99 +
100 // Flow magic to verify the exports of this file match the original version.
101 ((((null: any): ExportsType): FeatureFlagsType): ExportsType);
packages/shared/forks/ReactFeatureFlags.test-renderer.js
+1
@@ -99,6 +99,7 @@ export const enableInfiniteRenderLoopDetection = false;
99 const __NEXT_MAJOR__ = __EXPERIMENTAL__;
100 export const enableRefAsProp = __NEXT_MAJOR__;
101 export const enableReactTestRendererWarning = false;
102 +export const enableBigIntSupport = __NEXT_MAJOR__;
103
104 // Flow magic to verify the exports of this file match the original version.
105 ((((null: any): ExportsType): FeatureFlagsType): ExportsType);
packages/shared/forks/ReactFeatureFlags.test-renderer.native.js
+2
@@ -91,5 +91,7 @@ export const enableRefAsProp = false;
91
92 export const enableReactTestRendererWarning = false;
93
94 +export const enableBigIntSupport = false;
95 +
96 // Flow magic to verify the exports of this file match the original version.
97 ((((null: any): ExportsType): FeatureFlagsType): ExportsType);
packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
+2
@@ -94,5 +94,7 @@ export const enableRefAsProp = false;
94
95 export const enableReactTestRendererWarning = false;
96
97 +export const enableBigIntSupport = false;
98 +
99 // Flow magic to verify the exports of this file match the original version.
100 ((((null: any): ExportsType): FeatureFlagsType): ExportsType);
packages/shared/forks/ReactFeatureFlags.www.js
+2
@@ -119,5 +119,7 @@ export const enableServerComponentLogs = true;
119
120 export const enableReactTestRendererWarning = false;
121
122 +export const enableBigIntSupport = false;
123 +
124 // Flow magic to verify the exports of this file match the original version.
125 ((((null: any): ExportsType): FeatureFlagsType): ExportsType);