From 173005537568d4c9e827fe93fce16c59d27ddc2a Mon Sep 17 00:00:00 2001 From: Sebastian Silbermann Date: Sun, 18 Feb 2024 14:58:12 +0100 Subject: [PATCH] Add feature flag for experimental release channel --- .../src/client/ReactDOMComponent.js | 13 +++++-- .../src/client/ReactDOMOption.js | 3 +- .../src/client/ReactFiberConfigDOM.js | 3 +- .../src/client/ToStringValue.js | 8 ++++- .../src/server/ReactFizzConfigDOM.js | 22 ++++++++---- .../src/server/escapeTextForBrowser.js | 3 +- .../src/__tests__/ReactDOMFiber-test.js | 1 + .../src/__tests__/ReactDOMFizzServer-test.js | 14 ++++++-- .../src/__tests__/ReactDOMInput-test.js | 3 ++ .../src/__tests__/ReactDOMOption-test.js | 1 + .../ReactDOMServerIntegrationBasic-test.js | 8 +++-- .../ReactDOMServerIntegrationInput-test.js | 12 +++++-- .../ReactDOMServerIntegrationSelect-test.js | 4 ++- .../ReactDOMServerIntegrationTextarea-test.js | 11 ++++-- .../src/__tests__/ReactDOMTextarea-test.js | 1 + .../src/__tests__/ReactMultiChildText-test.js | 11 ++++-- .../react-reconciler/src/ReactChildFiber.js | 9 ++--- .../src/__tests__/ReactTopLevelText-test.js | 1 + packages/react-server/src/ReactFizzServer.js | 6 +++- packages/react/src/ReactChildren.js | 7 +++- .../react/src/__tests__/ReactChildren-test.js | 36 +++++++++++++------ packages/shared/ReactFeatureFlags.js | 2 ++ .../forks/ReactFeatureFlags.native-fb.js | 2 ++ .../forks/ReactFeatureFlags.native-oss.js | 2 ++ .../forks/ReactFeatureFlags.test-renderer.js | 2 ++ .../ReactFeatureFlags.test-renderer.native.js | 2 ++ .../ReactFeatureFlags.test-renderer.www.js | 2 ++ .../shared/forks/ReactFeatureFlags.www.js | 1 + 28 files changed, 148 insertions(+), 42 deletions(-) diff --git a/packages/react-dom-bindings/src/client/ReactDOMComponent.js b/packages/react-dom-bindings/src/client/ReactDOMComponent.js index 4b987035d9550..063f4819139c4 100644 --- a/packages/react-dom-bindings/src/client/ReactDOMComponent.js +++ b/packages/react-dom-bindings/src/client/ReactDOMComponent.js @@ -66,6 +66,7 @@ import {validateProperties as validateUnknownProperties} from '../shared/ReactDO import sanitizeURL from '../shared/sanitizeURL'; import { + enableBigIntSupport, enableCustomElementPropertySupport, enableClientRenderFallbackOnTextMismatch, enableFormActions, @@ -397,7 +398,10 @@ function setProp( if (canSetTextContent) { setTextContent(domElement, value); } - } else if (typeof value === 'number' || typeof value === 'bigint') { + } else if ( + typeof value === 'number' || + (enableBigIntSupport && typeof value === 'bigint') + ) { if (__DEV__) { // $FlowFixMe[unsafe-addition] Flow doesn't want us to use `+` operator with string and bigint validateTextNesting('' + value, tag); @@ -955,7 +959,10 @@ function setPropOnCustomElement( case 'children': { if (typeof value === 'string') { setTextContent(domElement, value); - } else if (typeof value === 'number' || typeof value === 'bigint') { + } else if ( + typeof value === 'number' || + (enableBigIntSupport && typeof value === 'bigint') + ) { // $FlowFixMe[unsafe-addition] Flow doesn't want us to use `+` operator with string and bigint setTextContent(domElement, '' + value); } @@ -2818,7 +2825,7 @@ export function diffHydratedProperties( if ( typeof children === 'string' || typeof children === 'number' || - typeof children === 'bigint' + (enableBigIntSupport && typeof children === 'bigint') ) { // $FlowFixMe[unsafe-addition] Flow doesn't want us to use `+` operator with string and bigint if (domElement.textContent !== '' + children) { diff --git a/packages/react-dom-bindings/src/client/ReactDOMOption.js b/packages/react-dom-bindings/src/client/ReactDOMOption.js index ae7a7ba999c06..10d04bd6f7eb2 100644 --- a/packages/react-dom-bindings/src/client/ReactDOMOption.js +++ b/packages/react-dom-bindings/src/client/ReactDOMOption.js @@ -8,6 +8,7 @@ */ import {Children} from 'react'; +import {enableBigIntSupport} from 'shared/ReactFeatureFlags'; let didWarnSelectedSetOnOption = false; let didWarnInvalidChild = false; @@ -29,7 +30,7 @@ export function validateOptionProps(element: Element, props: Object) { if ( typeof child === 'string' || typeof child === 'number' || - typeof child === 'bigint' + (enableBigIntSupport && typeof child === 'bigint') ) { return; } diff --git a/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js b/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js index d92814da9aed1..ebc850221b18e 100644 --- a/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js +++ b/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js @@ -89,6 +89,7 @@ import { import {retryIfBlockedOn} from '../events/ReactDOMEventReplaying'; import { + enableBigIntSupport, enableCreateEventHandleAPI, enableScopeAPI, enableFloat, @@ -548,7 +549,7 @@ export function shouldSetTextContent(type: string, props: Props): boolean { type === 'noscript' || typeof props.children === 'string' || typeof props.children === 'number' || - typeof props.children === 'bigint' || + (enableBigIntSupport && typeof props.children === 'bigint') || (typeof props.dangerouslySetInnerHTML === 'object' && props.dangerouslySetInnerHTML !== null && props.dangerouslySetInnerHTML.__html != null) diff --git a/packages/react-dom-bindings/src/client/ToStringValue.js b/packages/react-dom-bindings/src/client/ToStringValue.js index f6ebd55e23702..e1fc51b775dd4 100644 --- a/packages/react-dom-bindings/src/client/ToStringValue.js +++ b/packages/react-dom-bindings/src/client/ToStringValue.js @@ -8,6 +8,7 @@ */ import {checkFormFieldValueStringCoercion} from 'shared/CheckStringCoercion'; +import {enableBigIntSupport} from 'shared/ReactFeatureFlags'; export opaque type ToStringValue = | boolean @@ -29,9 +30,14 @@ export function toString(value: ToStringValue): string { export function getToStringValue(value: mixed): ToStringValue { switch (typeof value) { + case 'bigint': + if (!enableBigIntSupport) { + // bigint is assigned as empty string + return ''; + } + // fallthrough for BigInt support case 'boolean': case 'number': - case 'bigint': case 'string': case 'undefined': return value; diff --git a/packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js b/packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js index 8f0f7b677fa9d..01d0c7e0a5d14 100644 --- a/packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js +++ b/packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js @@ -28,6 +28,7 @@ import { import {Children} from 'react'; import { + enableBigIntSupport, enableFilterEmptyStringAttributesDOM, enableCustomElementPropertySupport, enableFloat, @@ -1626,7 +1627,8 @@ function flattenOptionChildren(children: mixed): string { !didWarnInvalidOptionChildren && typeof child !== 'string' && typeof child !== 'number' && - typeof child !== 'bigint' + ((enableBigIntSupport && typeof child !== 'bigint') || + !enableBigIntSupport) ) { didWarnInvalidOptionChildren = true; console.error( @@ -2960,36 +2962,40 @@ function pushTitle( if (Array.isArray(children) && children.length > 1) { console.error( - 'React expects the `children` prop of tags to be a string, number, bigint, or object with a novel `toString` method but found an Array with length %s instead.' + + '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.' + ' 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}. 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: {`hello ${nameOfUser}`}.', + enableBigIntSupport ? ', bigint' : '', children.length, ); } else if (typeof child === 'function' || typeof child === 'symbol') { const childType = typeof child === 'function' ? 'a Function' : 'a Sybmol'; console.error( - 'React expect children of tags to be a string, number, bigint, or object with a novel `toString` method but found %s instead.' + + 'React expect children of <title> tags to be a string, number%s, or object with a novel `toString` method but found %s 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.', + enableBigIntSupport ? ', bigint' : '', childType, ); } else if (child && child.toString === {}.toString) { if (child.$$typeof != null) { console.error( - 'React expects the `children` prop of <title> tags to be a string, number, bigint, or object with a novel `toString` method but found an object that appears to be' + + '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' + ' 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.', + enableBigIntSupport ? ', bigint' : '', ); } else { console.error( - 'React expects the `children` prop of <title> tags to be a string, number, bigint, or object with a novel `toString` method but found an object that does not implement' + + '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' + ' 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>.', + enableBigIntSupport ? ', bigint' : '', ); } } @@ -3124,14 +3130,16 @@ function pushStartTitle( childForValidation != null && typeof childForValidation !== 'string' && typeof childForValidation !== 'number' && - typeof childForValidation !== 'bigint' + ((enableBigIntSupport && typeof childForValidation !== 'bigint') || + !enableBigIntSupport) ) { console.error( - 'A title element received a value that was not a string or number or bigint for children. ' + + 'A title element received a value that was not a string or number%s 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', + enableBigIntSupport ? ' or bigint' : '', ); } } diff --git a/packages/react-dom-bindings/src/server/escapeTextForBrowser.js b/packages/react-dom-bindings/src/server/escapeTextForBrowser.js index 4514bf66831d7..6fd43c01c5f69 100644 --- a/packages/react-dom-bindings/src/server/escapeTextForBrowser.js +++ b/packages/react-dom-bindings/src/server/escapeTextForBrowser.js @@ -39,6 +39,7 @@ */ import {checkHtmlStringCoercion} from 'shared/CheckStringCoercion'; +import {enableBigIntSupport} from 'shared/ReactFeatureFlags'; const matchHtmlRegExp = /["'&<>]/; @@ -109,7 +110,7 @@ function escapeTextForBrowser(text: string | number | boolean): string { if ( typeof text === 'boolean' || typeof text === 'number' || - typeof text === 'bigint' + (enableBigIntSupport && 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 diff --git a/packages/react-dom/src/__tests__/ReactDOMFiber-test.js b/packages/react-dom/src/__tests__/ReactDOMFiber-test.js index 2000ed76e03c8..2882cab2e9991 100644 --- a/packages/react-dom/src/__tests__/ReactDOMFiber-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMFiber-test.js @@ -60,6 +60,7 @@ describe('ReactDOMFiber', () => { expect(container.textContent).toEqual('10'); }); + // @gate enableBigIntSupport it('should render bigints as children', async () => { const Box = ({value}) => <div>{value}</div>; diff --git a/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js b/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js index 07c6dfa61d4af..126988076d198 100644 --- a/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js @@ -3373,6 +3373,7 @@ describe('ReactDOMFizzServer', () => { ); }); + // @gate enableBigIntSupport it('Supports bigint', async () => { await act(async () => { const {pipe} = ReactDOMFizzServer.renderToPipeableStream( @@ -5679,6 +5680,7 @@ describe('ReactDOMFizzServer', () => { expect(getVisibleChildren(document.head)).toEqual(<title>4); }); + // @gate enableBigIntSupport it('should accept a single bigint child', async () => { // a Single number child function App() { @@ -5748,7 +5750,9 @@ describe('ReactDOMFizzServer', () => { pipe(writable); }); }).toErrorDev([ - 'React expects the `children` prop of tags to be a string, number, bigint, 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}. 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: {`hello ${nameOfUser}`}.', + 'React expects the `children` prop of tags to be a string, number' + + gate(flags => (flags.enableBigIntSupport ? ', bigint' : '')) + + ', 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}. 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: {`hello ${nameOfUser}`}.', ]); if (gate(flags => flags.enableFloat)) { @@ -5808,7 +5812,9 @@ describe('ReactDOMFizzServer', () => { pipe(writable); }); }).toErrorDev([ - 'React expects the `children` prop of tags to be a string, number, bigint, 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.', + 'React expects the `children` prop of <title> tags to be a string, number' + + gate(flags => (flags.enableBigIntSupport ? ', bigint' : '')) + + ', 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.', ]); } else { await expect(async () => { @@ -5864,7 +5870,9 @@ describe('ReactDOMFizzServer', () => { pipe(writable); }); }).toErrorDev([ - 'React expects the `children` prop of <title> tags to be a string, number, bigint, 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>.', + 'React expects the `children` prop of <title> tags to be a string, number' + + gate(flags => (flags.enableBigIntSupport ? ', bigint' : '')) + + ', 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>.', ]); } else { await expect(async () => { diff --git a/packages/react-dom/src/__tests__/ReactDOMInput-test.js b/packages/react-dom/src/__tests__/ReactDOMInput-test.js index e15c9e224f943..261b60b85cfa7 100644 --- a/packages/react-dom/src/__tests__/ReactDOMInput-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMInput-test.js @@ -657,6 +657,7 @@ describe('ReactDOMInput', () => { expect(div.firstChild.getAttribute('defaultValue')).toBe(null); }); + // @gate enableBigIntSupport it('should render bigint defaultValue for SSR', () => { const markup = ReactDOMServer.renderToString( <input type="text" defaultValue={5n} />, @@ -676,6 +677,7 @@ describe('ReactDOMInput', () => { expect(div.firstChild.getAttribute('defaultValue')).toBe(null); }); + // @gate enableBigIntSupport it('should render bigint value for SSR', () => { const element = <input type="text" value={5n} onChange={() => {}} />; const markup = ReactDOMServer.renderToString(element); @@ -849,6 +851,7 @@ describe('ReactDOMInput', () => { expect(node.value).toBe('0'); }); + // @gate enableBigIntSupport it('should display `value` of bigint 5', async () => { await act(() => { root.render(<input type="text" value={5n} onChange={emptyFunction} />); diff --git a/packages/react-dom/src/__tests__/ReactDOMOption-test.js b/packages/react-dom/src/__tests__/ReactDOMOption-test.js index 0b3fbebf63947..80f8a821cb11d 100644 --- a/packages/react-dom/src/__tests__/ReactDOMOption-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMOption-test.js @@ -171,6 +171,7 @@ describe('ReactDOMOption', () => { expect(node.value).toBe('hello'); }); + // @gate enableBigIntSupport it('should support bigint values', () => { const node = ReactTestUtils.renderIntoDocument(<option>{5n}</option>); expect(node.innerHTML).toBe('5'); diff --git a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationBasic-test.js b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationBasic-test.js index 3d66f70bfdc62..1abca1c835f99 100644 --- a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationBasic-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationBasic-test.js @@ -77,8 +77,12 @@ describe('ReactDOMServerIntegration', () => { itRenders('a bigint', async render => { const e = await render(42n); - expect(e.nodeType).toBe(3); - expect(e.nodeValue).toMatch('42'); + if (gate(flags => flags.enableBigIntSupport)) { + expect(e.nodeType).toBe(3); + expect(e.nodeValue).toMatch('42'); + } else { + expect(e).toBe(null); + } }); itRenders('an array with one child', async render => { diff --git a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationInput-test.js b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationInput-test.js index 1243b193be3c8..2686c2f2df550 100644 --- a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationInput-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationInput-test.js @@ -35,7 +35,8 @@ function initModules() { }; } -const {resetModules, itRenders} = ReactDOMServerIntegrationUtils(initModules); +const {resetModules, itRenders, serverRender, streamRender} = + ReactDOMServerIntegrationUtils(initModules); // TODO: Run this in React Fire mode after we figure out the SSR behavior. const desc = disableInputAttributeSyncing ? xdescribe : describe; @@ -50,8 +51,15 @@ desc('ReactDOMServerIntegrationInput', () => { }); itRenders('an input with a bigint value and an onChange', async render => { + console.log(gate(flags => flags.enableBigIntSupport)); const e = await render(<input value={5n} onChange={() => {}} />); - expect(e.value).toBe('5'); + expect(e.value).toBe( + gate(flags => flags.enableBigIntSupport) || + render === serverRender || + render === streamRender + ? '5' + : '', + ); }); itRenders('an input with a value and readOnly', async render => { diff --git a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationSelect-test.js b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationSelect-test.js index a1b547135655b..f34a7d5193f73 100644 --- a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationSelect-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationSelect-test.js @@ -227,7 +227,9 @@ describe('ReactDOMServerIntegrationSelect', () => { </select>, ); const option = e.options[0]; - expect(option.textContent).toBe('A B 5'); + expect(option.textContent).toBe( + gate(flags => flags.enableBigIntSupport) ? 'A B 5' : 'A B ', + ); expect(option.value).toBe('bar'); expect(option.selected).toBe(true); }); diff --git a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationTextarea-test.js b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationTextarea-test.js index c03ea0d384724..e8163d52e736f 100644 --- a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationTextarea-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationTextarea-test.js @@ -33,7 +33,8 @@ function initModules() { }; } -const {resetModules, itRenders} = ReactDOMServerIntegrationUtils(initModules); +const {resetModules, itRenders, serverRender, streamRender} = + ReactDOMServerIntegrationUtils(initModules); describe('ReactDOMServerIntegrationTextarea', () => { beforeEach(() => { @@ -53,7 +54,13 @@ describe('ReactDOMServerIntegrationTextarea', () => { itRenders('a textarea with a bigint value and an onChange', async render => { const e = await render(<textarea value={5n} onChange={() => {}} />); expect(e.getAttribute('value')).toBe(null); - expect(e.value).toBe('5'); + expect(e.value).toBe( + gate(flags => flags.enableBigIntSupport) || + render === serverRender || + render === streamRender + ? '5' + : '', + ); }); itRenders('a textarea with a value of undefined', async render => { diff --git a/packages/react-dom/src/__tests__/ReactDOMTextarea-test.js b/packages/react-dom/src/__tests__/ReactDOMTextarea-test.js index 7b818fdde2fc3..88c574b4cb2e5 100644 --- a/packages/react-dom/src/__tests__/ReactDOMTextarea-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMTextarea-test.js @@ -80,6 +80,7 @@ describe('ReactDOMTextarea', () => { expect(node.value).toBe('0'); }); + // @gate enableBigIntSupport it('should display `defaultValue` of bigint 0', async () => { const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); diff --git a/packages/react-dom/src/__tests__/ReactMultiChildText-test.js b/packages/react-dom/src/__tests__/ReactMultiChildText-test.js index b1fff66bf94b9..7cf2c048c504e 100644 --- a/packages/react-dom/src/__tests__/ReactMultiChildText-test.js +++ b/packages/react-dom/src/__tests__/ReactMultiChildText-test.js @@ -90,7 +90,6 @@ describe('ReactMultiChildText', () => { true, [], 0, '0', 1.2, '1.2', - 10n, '10', '', [], 'foo', 'foo', @@ -101,7 +100,6 @@ describe('ReactMultiChildText', () => { [true], [], [0], ['0'], [1.2], ['1.2'], - [10n], ['10'], [''], [], ['foo'], ['foo'], [<div />], [<div />], @@ -176,6 +174,15 @@ describe('ReactMultiChildText', () => { ]); }); + // @gate enableBigIntSupport + it('should correctly handle bigint children for render and update', async () => { + // prettier-ignore + await testAllPermutations([ + 10n, '10', + [10n], ['10'] + ]); + }); + it('should throw if rendering both HTML and children', async () => { const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); diff --git a/packages/react-reconciler/src/ReactChildFiber.js b/packages/react-reconciler/src/ReactChildFiber.js index 29ac0bd2d69cf..68eb705928be7 100644 --- a/packages/react-reconciler/src/ReactChildFiber.js +++ b/packages/react-reconciler/src/ReactChildFiber.js @@ -25,6 +25,7 @@ import { Forked, PlacementDEV, } from './ReactFiberFlags'; +import {enableBigIntSupport} from 'shared/ReactFeatureFlags'; import { getIteratorFn, REACT_ELEMENT_TYPE, @@ -559,7 +560,7 @@ function createChildReconciler( if ( (typeof newChild === 'string' && newChild !== '') || typeof newChild === 'number' || - typeof newChild === 'bigint' + (enableBigIntSupport && 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 @@ -678,7 +679,7 @@ function createChildReconciler( if ( (typeof newChild === 'string' && newChild !== '') || typeof newChild === 'number' || - typeof newChild === 'bigint' + (enableBigIntSupport && 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 @@ -800,7 +801,7 @@ function createChildReconciler( if ( (typeof newChild === 'string' && newChild !== '') || typeof newChild === 'number' || - typeof newChild === 'bigint' + (enableBigIntSupport && 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. @@ -1614,7 +1615,7 @@ function createChildReconciler( if ( (typeof newChild === 'string' && newChild !== '') || typeof newChild === 'number' || - typeof newChild === 'bigint' + (enableBigIntSupport && 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 b1d8dd5e01082..9c5de7bd2ee5f 100644 --- a/packages/react-reconciler/src/__tests__/ReactTopLevelText-test.js +++ b/packages/react-reconciler/src/__tests__/ReactTopLevelText-test.js @@ -40,6 +40,7 @@ describe('ReactTopLevelText', () => { expect(ReactNoop).toMatchRenderedOutput('10'); }); + // @gate enableBigIntSupport it('should render a component returning bigints directly from render', async () => { const Text = ({value}) => value; ReactNoop.render(<Text value={10n} />); diff --git a/packages/react-server/src/ReactFizzServer.js b/packages/react-server/src/ReactFizzServer.js index c5f1955ce85ba..6541e80c927f8 100644 --- a/packages/react-server/src/ReactFizzServer.js +++ b/packages/react-server/src/ReactFizzServer.js @@ -140,6 +140,7 @@ import ReactSharedInternals from 'shared/ReactSharedInternals'; import { disableLegacyContext, disableModulePatternComponents, + enableBigIntSupport, enableScopeAPI, enableSuspenseAvoidThisFallbackFizz, enableFloat, @@ -2311,7 +2312,10 @@ function renderNodeDestructive( return; } - if (typeof node === 'number' || typeof node === 'bigint') { + if ( + typeof node === 'number' || + (enableBigIntSupport && typeof node === 'bigint') + ) { const segment = task.blockedSegment; if (segment === null) { // We assume a text node doesn't have a representation in the replay set, diff --git a/packages/react/src/ReactChildren.js b/packages/react/src/ReactChildren.js index aa5547f627e4c..25ee5e92ad8e3 100644 --- a/packages/react/src/ReactChildren.js +++ b/packages/react/src/ReactChildren.js @@ -16,6 +16,7 @@ import type { } from 'shared/ReactTypes'; import isArray from 'shared/isArray'; +import {enableBigIntSupport} from 'shared/ReactFeatureFlags'; import { getIteratorFn, REACT_ELEMENT_TYPE, @@ -163,9 +164,13 @@ function mapIntoArray( invokeCallback = true; } else { switch (type) { + case 'bigint': + if (!enableBigIntSupport) { + break; + } + // fallthrough for enabled BigInt support 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 deaaf9f856987..532cc3e8ec232 100644 --- a/packages/react/src/__tests__/ReactChildren-test.js +++ b/packages/react/src/__tests__/ReactChildren-test.js @@ -188,7 +188,9 @@ describe('ReactChildren', () => { ); function assertCalls() { - expect(callback).toHaveBeenCalledTimes(10); + expect(callback).toHaveBeenCalledTimes( + gate(flags => flags.enableBigIntSupport) ? 10 : 9, + ); expect(callback).toHaveBeenCalledWith(div, 0); expect(callback).toHaveBeenCalledWith(span, 1); expect(callback).toHaveBeenCalledWith(a, 2); @@ -198,7 +200,11 @@ describe('ReactChildren', () => { expect(callback).toHaveBeenCalledWith(null, 6); expect(callback).toHaveBeenCalledWith(null, 7); expect(callback).toHaveBeenCalledWith(null, 8); - expect(callback).toHaveBeenCalledWith(9n, 9); + if (gate(flags => flags.enableBigIntSupport)) { + expect(callback).toHaveBeenCalledWith(9n, 9); + } else { + expect(callback).not.toHaveBeenCalledWith(9n, 9); + } callback.mockClear(); } @@ -211,14 +217,24 @@ describe('ReactChildren', () => { context, ); assertCalls(); - expect(mappedChildren).toEqual([ - <div key=".$divNode" />, - <span key=".1:0:$spanNode" />, - <a key=".2:$aNode" />, - 'string', - 1234, - 9n, - ]); + expect(mappedChildren).toEqual( + gate(flags => flags.enableBigIntSupport) + ? [ + <div key=".$divNode" />, + <span key=".1:0:$spanNode" />, + <a key=".2:$aNode" />, + 'string', + 1234, + 9n, + ] + : [ + <div key=".$divNode" />, + <span key=".1:0:$spanNode" />, + <a key=".2:$aNode" />, + 'string', + 1234, + ], + ); }); it('should be called for each child in nested structure', () => { diff --git a/packages/shared/ReactFeatureFlags.js b/packages/shared/ReactFeatureFlags.js index c2ffe2a652692..1437f2aaa447c 100644 --- a/packages/shared/ReactFeatureFlags.js +++ b/packages/shared/ReactFeatureFlags.js @@ -102,6 +102,8 @@ export const enableCPUSuspense = __EXPERIMENTAL__; export const enableFloat = true; +export const enableBigIntSupport = __EXPERIMENTAL__; + // Enables unstable_useMemoCache hook, intended as a compilation target for // auto-memoization. export const enableUseMemoCacheHook = __EXPERIMENTAL__; diff --git a/packages/shared/forks/ReactFeatureFlags.native-fb.js b/packages/shared/forks/ReactFeatureFlags.native-fb.js index 3a95817810fac..a650b50b86801 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-fb.js +++ b/packages/shared/forks/ReactFeatureFlags.native-fb.js @@ -86,6 +86,8 @@ export const enableTransitionTracing = false; export const enableFloat = true; +export const enableBigIntSupport = false; + export const useModernStrictMode = false; export const enableDO_NOT_USE_disableStrictPassiveEffect = false; export const enableFizzExternalRuntime = true; diff --git a/packages/shared/forks/ReactFeatureFlags.native-oss.js b/packages/shared/forks/ReactFeatureFlags.native-oss.js index 78c591c0aebf1..6cea48fe5af58 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-oss.js +++ b/packages/shared/forks/ReactFeatureFlags.native-oss.js @@ -72,6 +72,8 @@ export const enableTransitionTracing = false; export const enableFloat = true; +export const enableBigIntSupport = false; + export const useModernStrictMode = false; export const enableDO_NOT_USE_disableStrictPassiveEffect = false; export const enableFizzExternalRuntime = true; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.js index 93018e5c091da..3663b5c336214 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.js @@ -72,6 +72,8 @@ export const enableTransitionTracing = false; export const enableFloat = true; +export const enableBigIntSupport = false; + export const useModernStrictMode = false; export const enableDO_NOT_USE_disableStrictPassiveEffect = false; export const enableFizzExternalRuntime = true; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js index 4390303eeb181..9806cc305b8a0 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js @@ -71,6 +71,8 @@ export const enableTransitionTracing = false; export const enableFloat = true; +export const enableBigIntSupport = false; + export const useModernStrictMode = false; export const enableDO_NOT_USE_disableStrictPassiveEffect = false; export const enableDeferRootSchedulingToMicrotask = false; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js index 8e21c3b1a6286..bda46404fa71e 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js @@ -72,6 +72,8 @@ export const enableTransitionTracing = false; export const enableFloat = true; +export const enableBigIntSupport = false; + export const useModernStrictMode = false; export const enableDO_NOT_USE_disableStrictPassiveEffect = false; export const enableFizzExternalRuntime = false; diff --git a/packages/shared/forks/ReactFeatureFlags.www.js b/packages/shared/forks/ReactFeatureFlags.www.js index 0a18e4300be43..839c35641a535 100644 --- a/packages/shared/forks/ReactFeatureFlags.www.js +++ b/packages/shared/forks/ReactFeatureFlags.www.js @@ -56,6 +56,7 @@ export const enableSuspenseAvoidThisFallbackFizz = false; export const enableCustomElementPropertySupport = true; export const enableCPUSuspense = true; export const enableFloat = true; +export const enableBigIntSupport = false; export const enableUseMemoCacheHook = true; export const enableUseEffectEventHook = true; export const enableClientRenderFallbackOnTextMismatch = false;