Skip to content

Commit

Permalink
Add support for rendering BigInt (#24580)
Browse files Browse the repository at this point in the history
  • Loading branch information
eps1lon authored Feb 26, 2024
1 parent 6c3b8db commit 2f240c9
Show file tree
Hide file tree
Showing 30 changed files with 389 additions and 36 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -58,5 +58,9 @@ export default function UnserializableProps(): React.Node {
}

function ChildComponent(props: any) {
return null;
return (
<>
<div>{props.bigInt}</div>
</>
);
}
23 changes: 19 additions & 4 deletions packages/react-dom-bindings/src/client/ReactDOMComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ import {validateProperties as validateUnknownProperties} from '../shared/ReactDO
import sanitizeURL from '../shared/sanitizeURL';

import {
enableBigIntSupport,
enableCustomElementPropertySupport,
enableClientRenderFallbackOnTextMismatch,
enableFormActions,
Expand Down Expand Up @@ -326,7 +327,7 @@ function normalizeMarkupForTextOrAttribute(markup: mixed): string {

export function checkForUnmatchedText(
serverText: string,
clientText: string | number,
clientText: string | number | bigint,
isConcurrentMode: boolean,
shouldWarnDev: boolean,
) {
Expand Down Expand Up @@ -397,12 +398,17 @@ function setProp(
if (canSetTextContent) {
setTextContent(domElement, value);
}
} else if (typeof value === 'number') {
} 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);
}
const canSetTextContent = tag !== 'body';
if (canSetTextContent) {
// $FlowFixMe[unsafe-addition] Flow doesn't want us to use `+` operator with string and bigint
setTextContent(domElement, '' + value);
}
}
Expand Down Expand Up @@ -955,7 +961,11 @@ function setPropOnCustomElement(
case 'children': {
if (typeof value === 'string') {
setTextContent(domElement, value);
} else if (typeof value === 'number') {
} 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);
}
break;
Expand Down Expand Up @@ -2817,7 +2827,12 @@ export function diffHydratedProperties(
// even listeners these nodes might be wired up to.
// TODO: Warn if there is more than a single textNode as a child.
// TODO: Should we use domElement.firstChild.nodeValue to compare?
if (typeof children === 'string' || typeof children === 'number') {
if (
typeof children === 'string' ||
typeof children === 'number' ||
(enableBigIntSupport && typeof children === 'bigint')
) {
// $FlowFixMe[unsafe-addition] Flow doesn't want us to use `+` operator with string and bigint
if (domElement.textContent !== '' + children) {
if (props.suppressHydrationWarning !== true) {
checkForUnmatchedText(
Expand Down
7 changes: 6 additions & 1 deletion packages/react-dom-bindings/src/client/ReactDOMOption.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
*/

import {Children} from 'react';
import {enableBigIntSupport} from 'shared/ReactFeatureFlags';

let didWarnSelectedSetOnOption = false;
let didWarnInvalidChild = false;
Expand All @@ -26,7 +27,11 @@ export function validateOptionProps(element: Element, props: Object) {
if (child == null) {
return;
}
if (typeof child === 'string' || typeof child === 'number') {
if (
typeof child === 'string' ||
typeof child === 'number' ||
(enableBigIntSupport && typeof child === 'bigint')
) {
return;
}
if (!didWarnInvalidChild) {
Expand Down
2 changes: 2 additions & 0 deletions packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ import {
import {retryIfBlockedOn} from '../events/ReactDOMEventReplaying';

import {
enableBigIntSupport,
enableCreateEventHandleAPI,
enableScopeAPI,
enableFloat,
Expand Down Expand Up @@ -548,6 +549,7 @@ export function shouldSetTextContent(type: string, props: Props): boolean {
type === 'noscript' ||
typeof props.children === 'string' ||
typeof props.children === 'number' ||
(enableBigIntSupport && typeof props.children === 'bigint') ||
(typeof props.dangerouslySetInnerHTML === 'object' &&
props.dangerouslySetInnerHTML !== null &&
props.dangerouslySetInnerHTML.__html != null)
Expand Down
8 changes: 8 additions & 0 deletions packages/react-dom-bindings/src/client/ToStringValue.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@
*/

import {checkFormFieldValueStringCoercion} from 'shared/CheckStringCoercion';
import {enableBigIntSupport} from 'shared/ReactFeatureFlags';

export opaque type ToStringValue =
| boolean
| number
| bigint
| Object
| string
| null
Expand All @@ -28,6 +30,12 @@ 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 'string':
Expand Down
24 changes: 17 additions & 7 deletions packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
import {Children} from 'react';

import {
enableBigIntSupport,
enableFilterEmptyStringAttributesDOM,
enableCustomElementPropertySupport,
enableFloat,
Expand Down Expand Up @@ -1626,7 +1627,9 @@ function flattenOptionChildren(children: mixed): string {
if (
!didWarnInvalidOptionChildren &&
typeof child !== 'string' &&
typeof child !== 'number'
typeof child !== 'number' &&
((enableBigIntSupport && typeof child !== 'bigint') ||
!enableBigIntSupport)
) {
didWarnInvalidOptionChildren = true;
console.error(
Expand Down Expand Up @@ -2960,36 +2963,40 @@ function pushTitle(

if (Array.isArray(children) && children.length > 1) {
console.error(
'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.' +
'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}</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>.',
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 <title> tags to be a string, number, 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, 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, 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' : '',
);
}
}
Expand Down Expand Up @@ -3123,14 +3130,17 @@ function pushStartTitle(
} else if (
childForValidation != null &&
typeof childForValidation !== 'string' &&
typeof childForValidation !== 'number'
typeof childForValidation !== 'number' &&
((enableBigIntSupport && typeof childForValidation !== 'bigint') ||
!enableBigIntSupport)
) {
console.error(
'A title element received a value that was not a string or number 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' : '',
);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
*/

import {checkHtmlStringCoercion} from 'shared/CheckStringCoercion';
import {enableBigIntSupport} from 'shared/ReactFeatureFlags';

const matchHtmlRegExp = /["'&<>]/;

Expand Down Expand Up @@ -106,7 +107,11 @@ function escapeHtml(string: string) {
* @return {string} An escaped string.
*/
function escapeTextForBrowser(text: string | number | boolean): string {
if (typeof text === 'boolean' || typeof text === 'number') {
if (
typeof text === 'boolean' ||
typeof text === 'number' ||
(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
// for numeric dom ids.
Expand Down
11 changes: 11 additions & 0 deletions packages/react-dom/src/__tests__/ReactDOMFiber-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,17 @@ describe('ReactDOMFiber', () => {
expect(container.textContent).toEqual('10');
});

// @gate enableBigIntSupport
it('should render bigints as children', async () => {
const Box = ({value}) => <div>{value}</div>;

await act(async () => {
root.render(<Box value={10n} />);
});

expect(container.textContent).toEqual('10');
});

it('should call an effect after mount/update (replacing render callback pattern)', async () => {
function Component() {
React.useEffect(() => {
Expand Down
Loading

0 comments on commit 2f240c9

Please sign in to comment.