diff --git a/packages/react-devtools-shared/src/__tests__/utils-test.js b/packages/react-devtools-shared/src/__tests__/utils-test.js index b035ce012b023..e481ff8b66f52 100644 --- a/packages/react-devtools-shared/src/__tests__/utils-test.js +++ b/packages/react-devtools-shared/src/__tests__/utils-test.js @@ -23,7 +23,7 @@ import { REACT_SUSPENSE_LIST_TYPE as SuspenseList, REACT_STRICT_MODE_TYPE as StrictMode, } from 'shared/ReactSymbols'; -import {createElement} from 'react/src/ReactElement'; +import {createElement} from 'react'; describe('utils', () => { describe('getDisplayName', () => { diff --git a/packages/react/src/ReactChildren.js b/packages/react/src/ReactChildren.js index adddda2ff3210..8b9219b9fb1f0 100644 --- a/packages/react/src/ReactChildren.js +++ b/packages/react/src/ReactChildren.js @@ -24,7 +24,7 @@ import { } from 'shared/ReactSymbols'; import {checkKeyStringCoercion} from 'shared/CheckStringCoercion'; -import {isValidElement, cloneAndReplaceKey} from './ReactElement'; +import {isValidElement, cloneAndReplaceKey} from './jsx/ReactJSXElement'; const SEPARATOR = '.'; const SUBSEPARATOR = ':'; diff --git a/packages/react/src/ReactClient.js b/packages/react/src/ReactClient.js index 85a9c1d692696..ff988a2a2cf3d 100644 --- a/packages/react/src/ReactClient.js +++ b/packages/react/src/ReactClient.js @@ -30,7 +30,7 @@ import { createFactory, cloneElement, isValidElement, -} from './ReactElement'; +} from './jsx/ReactJSXElement'; import {createContext} from './ReactContext'; import {lazy} from './ReactLazy'; import {forwardRef} from './ReactForwardRef'; diff --git a/packages/react/src/ReactElement.js b/packages/react/src/ReactElement.js deleted file mode 100644 index ab3df9600ab86..0000000000000 --- a/packages/react/src/ReactElement.js +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @flow - */ - -import { - createElement as createElementProd, - createFactory as createFactoryProd, - cloneElement as cloneElementProd, -} from './ReactElementProd'; - -import { - createElementWithValidation, - createFactoryWithValidation, - cloneElementWithValidation, -} from './ReactElementValidator'; - -export {isValidElement, cloneAndReplaceKey} from './ReactElementProd'; - -export const createElement: any = __DEV__ - ? createElementWithValidation - : createElementProd; -export const cloneElement: any = __DEV__ - ? cloneElementWithValidation - : cloneElementProd; -export const createFactory: any = __DEV__ - ? createFactoryWithValidation - : createFactoryProd; diff --git a/packages/react/src/ReactElementProd.js b/packages/react/src/ReactElementProd.js deleted file mode 100644 index 36c9381ef1ff4..0000000000000 --- a/packages/react/src/ReactElementProd.js +++ /dev/null @@ -1,406 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import getComponentNameFromType from 'shared/getComponentNameFromType'; -import {REACT_ELEMENT_TYPE} from 'shared/ReactSymbols'; -import assign from 'shared/assign'; -import hasOwnProperty from 'shared/hasOwnProperty'; -import {checkKeyStringCoercion} from 'shared/CheckStringCoercion'; - -import ReactCurrentOwner from './ReactCurrentOwner'; - -let specialPropKeyWarningShown, - specialPropRefWarningShown, - didWarnAboutStringRefs; - -if (__DEV__) { - didWarnAboutStringRefs = {}; -} - -function hasValidRef(config) { - if (__DEV__) { - if (hasOwnProperty.call(config, 'ref')) { - const getter = Object.getOwnPropertyDescriptor(config, 'ref').get; - if (getter && getter.isReactWarning) { - return false; - } - } - } - return config.ref !== undefined; -} - -function hasValidKey(config) { - if (__DEV__) { - if (hasOwnProperty.call(config, 'key')) { - const getter = Object.getOwnPropertyDescriptor(config, 'key').get; - if (getter && getter.isReactWarning) { - return false; - } - } - } - return config.key !== undefined; -} - -function defineKeyPropWarningGetter(props, displayName) { - const warnAboutAccessingKey = function () { - if (__DEV__) { - if (!specialPropKeyWarningShown) { - specialPropKeyWarningShown = true; - console.error( - '%s: `key` is not a prop. Trying to access it will result ' + - 'in `undefined` being returned. If you need to access the same ' + - 'value within the child component, you should pass it as a different ' + - 'prop. (https://reactjs.org/link/special-props)', - displayName, - ); - } - } - }; - warnAboutAccessingKey.isReactWarning = true; - Object.defineProperty(props, 'key', { - get: warnAboutAccessingKey, - configurable: true, - }); -} - -function defineRefPropWarningGetter(props, displayName) { - const warnAboutAccessingRef = function () { - if (__DEV__) { - if (!specialPropRefWarningShown) { - specialPropRefWarningShown = true; - console.error( - '%s: `ref` is not a prop. Trying to access it will result ' + - 'in `undefined` being returned. If you need to access the same ' + - 'value within the child component, you should pass it as a different ' + - 'prop. (https://reactjs.org/link/special-props)', - displayName, - ); - } - } - }; - warnAboutAccessingRef.isReactWarning = true; - Object.defineProperty(props, 'ref', { - get: warnAboutAccessingRef, - configurable: true, - }); -} - -function warnIfStringRefCannotBeAutoConverted(config) { - if (__DEV__) { - if ( - typeof config.ref === 'string' && - ReactCurrentOwner.current && - config.__self && - ReactCurrentOwner.current.stateNode !== config.__self - ) { - const componentName = getComponentNameFromType( - ReactCurrentOwner.current.type, - ); - - if (!didWarnAboutStringRefs[componentName]) { - console.error( - 'Component "%s" contains the string ref "%s". ' + - 'Support for string refs will be removed in a future major release. ' + - 'This case cannot be automatically converted to an arrow function. ' + - 'We ask you to manually fix this case by using useRef() or createRef() instead. ' + - 'Learn more about using refs safely here: ' + - 'https://reactjs.org/link/strict-mode-string-ref', - componentName, - config.ref, - ); - didWarnAboutStringRefs[componentName] = true; - } - } - } -} - -/** - * Factory method to create a new React element. This no longer adheres to - * the class pattern, so do not use new to call it. Also, instanceof check - * will not work. Instead test $$typeof field against Symbol.for('react.element') to check - * if something is a React Element. - * - * @param {*} type - * @param {*} props - * @param {*} key - * @param {string|object} ref - * @param {*} owner - * @param {*} self A *temporary* helper to detect places where `this` is - * different from the `owner` when React.createElement is called, so that we - * can warn. We want to get rid of owner and replace string `ref`s with arrow - * functions, and as long as `this` and owner are the same, there will be no - * change in behavior. - * @param {*} source An annotation object (added by a transpiler or otherwise) - * indicating filename, line number, and/or other information. - * @internal - */ -function ReactElement(type, key, ref, owner, props) { - const element = { - // This tag allows us to uniquely identify this as a React Element - $$typeof: REACT_ELEMENT_TYPE, - - // Built-in properties that belong on the element - type: type, - key: key, - ref: ref, - props: props, - - // Record the component responsible for creating this element. - _owner: owner, - }; - - if (__DEV__) { - // The validation flag is currently mutative. We put it on - // an external backing store so that we can freeze the whole object. - // This can be replaced with a WeakMap once they are implemented in - // commonly used development environments. - element._store = {}; - - // To make comparing ReactElements easier for testing purposes, we make - // the validation flag non-enumerable (where possible, which should - // include every environment we run tests in), so the test framework - // ignores it. - Object.defineProperty(element._store, 'validated', { - configurable: false, - enumerable: false, - writable: true, - value: false, - }); - // debugInfo contains Server Component debug information. - Object.defineProperty(element, '_debugInfo', { - configurable: false, - enumerable: false, - writable: true, - value: null, - }); - if (Object.freeze) { - Object.freeze(element.props); - Object.freeze(element); - } - } - - return element; -} - -/** - * Create and return a new ReactElement of the given type. - * See https://reactjs.org/docs/react-api.html#createelement - */ -export function createElement(type, config, children) { - let propName; - - // Reserved names are extracted - const props = {}; - - let key = null; - let ref = null; - - if (config != null) { - if (hasValidRef(config)) { - ref = config.ref; - - if (__DEV__) { - warnIfStringRefCannotBeAutoConverted(config); - } - } - if (hasValidKey(config)) { - if (__DEV__) { - checkKeyStringCoercion(config.key); - } - key = '' + config.key; - } - - // Remaining properties are added to a new props object - for (propName in config) { - if ( - hasOwnProperty.call(config, propName) && - // Skip over reserved prop names - propName !== 'key' && - // TODO: `ref` will no longer be reserved in the next major - propName !== 'ref' && - // ...and maybe these, too, though we currently rely on them for - // warnings and debug information in dev. Need to decide if we're OK - // with dropping them. In the jsx() runtime it's not an issue because - // the data gets passed as separate arguments instead of props, but - // it would be nice to stop relying on them entirely so we can drop - // them from the internal Fiber field. - propName !== '__self' && - propName !== '__source' - ) { - props[propName] = config[propName]; - } - } - } - - // Children can be more than one argument, and those are transferred onto - // the newly allocated props object. - const childrenLength = arguments.length - 2; - if (childrenLength === 1) { - props.children = children; - } else if (childrenLength > 1) { - const childArray = Array(childrenLength); - for (let i = 0; i < childrenLength; i++) { - childArray[i] = arguments[i + 2]; - } - if (__DEV__) { - if (Object.freeze) { - Object.freeze(childArray); - } - } - props.children = childArray; - } - - // Resolve default props - if (type && type.defaultProps) { - const defaultProps = type.defaultProps; - for (propName in defaultProps) { - if (props[propName] === undefined) { - props[propName] = defaultProps[propName]; - } - } - } - if (__DEV__) { - if (key || ref) { - const displayName = - typeof type === 'function' - ? type.displayName || type.name || 'Unknown' - : type; - if (key) { - defineKeyPropWarningGetter(props, displayName); - } - if (ref) { - defineRefPropWarningGetter(props, displayName); - } - } - } - return ReactElement(type, key, ref, ReactCurrentOwner.current, props); -} - -/** - * Return a function that produces ReactElements of a given type. - * See https://reactjs.org/docs/react-api.html#createfactory - */ -export function createFactory(type) { - const factory = createElement.bind(null, type); - // Expose the type on the factory and the prototype so that it can be - // easily accessed on elements. E.g. `.type === Foo`. - // This should not be named `constructor` since this may not be the function - // that created the element, and it may not even be a constructor. - // Legacy hook: remove it - factory.type = type; - return factory; -} - -export function cloneAndReplaceKey(oldElement, newKey) { - const newElement = ReactElement( - oldElement.type, - newKey, - oldElement.ref, - oldElement._owner, - oldElement.props, - ); - - return newElement; -} - -/** - * Clone and return a new ReactElement using element as the starting point. - * See https://reactjs.org/docs/react-api.html#cloneelement - */ -export function cloneElement(element, config, children) { - if (element === null || element === undefined) { - throw new Error( - `React.cloneElement(...): The argument must be a React element, but you passed ${element}.`, - ); - } - - let propName; - - // Original props are copied - const props = assign({}, element.props); - - // Reserved names are extracted - let key = element.key; - let ref = element.ref; - - // Owner will be preserved, unless ref is overridden - let owner = element._owner; - - if (config != null) { - if (hasValidRef(config)) { - // Silently steal the ref from the parent. - ref = config.ref; - owner = ReactCurrentOwner.current; - } - if (hasValidKey(config)) { - if (__DEV__) { - checkKeyStringCoercion(config.key); - } - key = '' + config.key; - } - - // Remaining properties override existing props - let defaultProps; - if (element.type && element.type.defaultProps) { - defaultProps = element.type.defaultProps; - } - for (propName in config) { - if ( - hasOwnProperty.call(config, propName) && - // Skip over reserved prop names - propName !== 'key' && - // TODO: `ref` will no longer be reserved in the next major - propName !== 'ref' && - // ...and maybe these, too, though we currently rely on them for - // warnings and debug information in dev. Need to decide if we're OK - // with dropping them. In the jsx() runtime it's not an issue because - // the data gets passed as separate arguments instead of props, but - // it would be nice to stop relying on them entirely so we can drop - // them from the internal Fiber field. - propName !== '__self' && - propName !== '__source' - ) { - if (config[propName] === undefined && defaultProps !== undefined) { - // Resolve default props - props[propName] = defaultProps[propName]; - } else { - props[propName] = config[propName]; - } - } - } - } - - // Children can be more than one argument, and those are transferred onto - // the newly allocated props object. - const childrenLength = arguments.length - 2; - if (childrenLength === 1) { - props.children = children; - } else if (childrenLength > 1) { - const childArray = Array(childrenLength); - for (let i = 0; i < childrenLength; i++) { - childArray[i] = arguments[i + 2]; - } - props.children = childArray; - } - - return ReactElement(element.type, key, ref, owner, props); -} - -/** - * Verifies the object is a ReactElement. - * See https://reactjs.org/docs/react-api.html#isvalidelement - * @param {?object} object - * @return {boolean} True if `object` is a ReactElement. - * @final - */ -export function isValidElement(object) { - return ( - typeof object === 'object' && - object !== null && - object.$$typeof === REACT_ELEMENT_TYPE - ); -} diff --git a/packages/react/src/ReactElementValidator.js b/packages/react/src/ReactElementValidator.js deleted file mode 100644 index 275be8efe2d66..0000000000000 --- a/packages/react/src/ReactElementValidator.js +++ /dev/null @@ -1,395 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -/** - * ReactElementValidator provides a wrapper around an element factory - * which validates the props passed to the element. This is intended to be - * used only in DEV and could be replaced by a static type checker for languages - * that support it. - */ - -import isValidElementType from 'shared/isValidElementType'; -import getComponentNameFromType from 'shared/getComponentNameFromType'; -import { - getIteratorFn, - REACT_FORWARD_REF_TYPE, - REACT_MEMO_TYPE, - REACT_FRAGMENT_TYPE, - REACT_ELEMENT_TYPE, -} from 'shared/ReactSymbols'; -import checkPropTypes from 'shared/checkPropTypes'; -import isArray from 'shared/isArray'; - -import ReactCurrentOwner from './ReactCurrentOwner'; -import {isValidElement, createElement, cloneElement} from './ReactElementProd'; -import {setExtraStackFrame} from './ReactDebugCurrentFrame'; -import {describeUnknownElementTypeFrameInDEV} from 'shared/ReactComponentStackFrame'; - -const REACT_CLIENT_REFERENCE = Symbol.for('react.client.reference'); - -function setCurrentlyValidatingElement(element) { - if (__DEV__) { - if (element) { - const owner = element._owner; - const stack = describeUnknownElementTypeFrameInDEV( - element.type, - owner ? owner.type : null, - ); - setExtraStackFrame(stack); - } else { - setExtraStackFrame(null); - } - } -} - -let propTypesMisspellWarningShown; - -if (__DEV__) { - propTypesMisspellWarningShown = false; -} - -function getDeclarationErrorAddendum() { - if (ReactCurrentOwner.current) { - const name = getComponentNameFromType(ReactCurrentOwner.current.type); - if (name) { - return '\n\nCheck the render method of `' + name + '`.'; - } - } - return ''; -} - -function getSourceInfoErrorAddendum(source) { - if (source !== undefined) { - const fileName = source.fileName.replace(/^.*[\\\/]/, ''); - const lineNumber = source.lineNumber; - return '\n\nCheck your code at ' + fileName + ':' + lineNumber + '.'; - } - return ''; -} - -function getSourceInfoErrorAddendumForProps(elementProps) { - if (elementProps !== null && elementProps !== undefined) { - return getSourceInfoErrorAddendum(elementProps.__source); - } - return ''; -} - -/** - * Warn if there's no key explicitly set on dynamic arrays of children or - * object keys are not valid. This allows us to keep track of children between - * updates. - */ -const ownerHasKeyUseWarning = {}; - -function getCurrentComponentErrorInfo(parentType) { - let info = getDeclarationErrorAddendum(); - - if (!info) { - const parentName = getComponentNameFromType(parentType); - if (parentName) { - info = `\n\nCheck the top-level render call using <${parentName}>.`; - } - } - return info; -} - -/** - * Warn if the element doesn't have an explicit key assigned to it. - * This element is in an array. The array could grow and shrink or be - * reordered. All children that haven't already been validated are required to - * have a "key" property assigned to it. Error statuses are cached so a warning - * will only be shown once. - * - * @internal - * @param {ReactElement} element Element that requires a key. - * @param {*} parentType element's parent's type. - */ -function validateExplicitKey(element, parentType) { - if (!element._store || element._store.validated || element.key != null) { - return; - } - element._store.validated = true; - - const currentComponentErrorInfo = getCurrentComponentErrorInfo(parentType); - if (ownerHasKeyUseWarning[currentComponentErrorInfo]) { - return; - } - ownerHasKeyUseWarning[currentComponentErrorInfo] = true; - - // Usually the current owner is the offender, but if it accepts children as a - // property, it may be the creator of the child that's responsible for - // assigning it a key. - let childOwner = ''; - if ( - element && - element._owner && - element._owner !== ReactCurrentOwner.current - ) { - // Give the component that originally created this child. - childOwner = ` It was passed a child from ${getComponentNameFromType( - element._owner.type, - )}.`; - } - - if (__DEV__) { - setCurrentlyValidatingElement(element); - console.error( - 'Each child in a list should have a unique "key" prop.' + - '%s%s See https://reactjs.org/link/warning-keys for more information.', - currentComponentErrorInfo, - childOwner, - ); - setCurrentlyValidatingElement(null); - } -} - -/** - * Ensure that every element either is passed in a static location, in an - * array with an explicit keys property defined, or in an object literal - * with valid key property. - * - * @internal - * @param {ReactNode} node Statically passed child of any type. - * @param {*} parentType node's parent's type. - */ -function validateChildKeys(node, parentType) { - if (typeof node !== 'object' || !node) { - return; - } - if (node.$$typeof === REACT_CLIENT_REFERENCE) { - // This is a reference to a client component so it's unknown. - } else if (isArray(node)) { - for (let i = 0; i < node.length; i++) { - const child = node[i]; - if (isValidElement(child)) { - validateExplicitKey(child, parentType); - } - } - } else if (isValidElement(node)) { - // This element was passed in a valid location. - if (node._store) { - node._store.validated = true; - } - } else { - const iteratorFn = getIteratorFn(node); - if (typeof iteratorFn === 'function') { - // Entry iterators used to provide implicit keys, - // but now we print a separate warning for them later. - if (iteratorFn !== node.entries) { - const iterator = iteratorFn.call(node); - let step; - while (!(step = iterator.next()).done) { - if (isValidElement(step.value)) { - validateExplicitKey(step.value, parentType); - } - } - } - } - } -} - -/** - * Given an element, validate that its props follow the propTypes definition, - * provided by the type. - * - * @param {ReactElement} element - */ -function validatePropTypes(element) { - if (__DEV__) { - const type = element.type; - if (type === null || type === undefined || typeof type === 'string') { - return; - } - if (type.$$typeof === REACT_CLIENT_REFERENCE) { - return; - } - let propTypes; - if (typeof type === 'function') { - propTypes = type.propTypes; - } else if ( - typeof type === 'object' && - (type.$$typeof === REACT_FORWARD_REF_TYPE || - // Note: Memo only checks outer props here. - // Inner props are checked in the reconciler. - type.$$typeof === REACT_MEMO_TYPE) - ) { - propTypes = type.propTypes; - } else { - return; - } - if (propTypes) { - // Intentionally inside to avoid triggering lazy initializers: - const name = getComponentNameFromType(type); - checkPropTypes(propTypes, element.props, 'prop', name, element); - } else if (type.PropTypes !== undefined && !propTypesMisspellWarningShown) { - propTypesMisspellWarningShown = true; - // Intentionally inside to avoid triggering lazy initializers: - const name = getComponentNameFromType(type); - console.error( - 'Component %s declared `PropTypes` instead of `propTypes`. Did you misspell the property assignment?', - name || 'Unknown', - ); - } - if ( - typeof type.getDefaultProps === 'function' && - !type.getDefaultProps.isReactClassApproved - ) { - console.error( - 'getDefaultProps is only used on classic React.createClass ' + - 'definitions. Use a static property named `defaultProps` instead.', - ); - } - } -} - -/** - * Given a fragment, validate that it can only be provided with fragment props - * @param {ReactElement} fragment - */ -function validateFragmentProps(fragment) { - if (__DEV__) { - const keys = Object.keys(fragment.props); - for (let i = 0; i < keys.length; i++) { - const key = keys[i]; - if (key !== 'children' && key !== 'key') { - setCurrentlyValidatingElement(fragment); - console.error( - 'Invalid prop `%s` supplied to `React.Fragment`. ' + - 'React.Fragment can only have `key` and `children` props.', - key, - ); - setCurrentlyValidatingElement(null); - break; - } - } - - if (fragment.ref !== null) { - setCurrentlyValidatingElement(fragment); - console.error('Invalid attribute `ref` supplied to `React.Fragment`.'); - setCurrentlyValidatingElement(null); - } - } -} - -export function createElementWithValidation(type, props, children) { - const validType = isValidElementType(type); - - // We warn in this case but don't throw. We expect the element creation to - // succeed and there will likely be errors in render. - if (!validType) { - let info = ''; - if ( - type === undefined || - (typeof type === 'object' && - type !== null && - Object.keys(type).length === 0) - ) { - info += - ' You likely forgot to export your component from the file ' + - "it's defined in, or you might have mixed up default and named imports."; - } - - const sourceInfo = getSourceInfoErrorAddendumForProps(props); - if (sourceInfo) { - info += sourceInfo; - } else { - info += getDeclarationErrorAddendum(); - } - - let typeString; - if (type === null) { - typeString = 'null'; - } else if (isArray(type)) { - typeString = 'array'; - } else if (type !== undefined && type.$$typeof === REACT_ELEMENT_TYPE) { - typeString = `<${getComponentNameFromType(type.type) || 'Unknown'} />`; - info = - ' Did you accidentally export a JSX literal instead of a component?'; - } else { - typeString = typeof type; - } - - if (__DEV__) { - console.error( - 'React.createElement: type is invalid -- expected a string (for ' + - 'built-in components) or a class/function (for composite ' + - 'components) but got: %s.%s', - typeString, - info, - ); - } - } - - const element = createElement.apply(this, arguments); - - // The result can be nullish if a mock or a custom function is used. - // TODO: Drop this when these are no longer allowed as the type argument. - if (element == null) { - return element; - } - - // Skip key warning if the type isn't valid since our key validation logic - // doesn't expect a non-string/function type and can throw confusing errors. - // We don't want exception behavior to differ between dev and prod. - // (Rendering will throw with a helpful message and as soon as the type is - // fixed, the key warnings will appear.) - if (validType) { - for (let i = 2; i < arguments.length; i++) { - validateChildKeys(arguments[i], type); - } - } - - if (type === REACT_FRAGMENT_TYPE) { - validateFragmentProps(element); - } else { - validatePropTypes(element); - } - - return element; -} - -let didWarnAboutDeprecatedCreateFactory = false; - -export function createFactoryWithValidation(type) { - const validatedFactory = createElementWithValidation.bind(null, type); - validatedFactory.type = type; - if (__DEV__) { - if (!didWarnAboutDeprecatedCreateFactory) { - didWarnAboutDeprecatedCreateFactory = true; - console.warn( - 'React.createFactory() is deprecated and will be removed in ' + - 'a future major release. Consider using JSX ' + - 'or use React.createElement() directly instead.', - ); - } - // Legacy hook: remove it - Object.defineProperty(validatedFactory, 'type', { - enumerable: false, - get: function () { - console.warn( - 'Factory.type is deprecated. Access the class directly ' + - 'before passing it to createFactory.', - ); - Object.defineProperty(this, 'type', { - value: type, - }); - return type; - }, - }); - } - - return validatedFactory; -} - -export function cloneElementWithValidation(element, props, children) { - const newElement = cloneElement.apply(this, arguments); - for (let i = 2; i < arguments.length; i++) { - validateChildKeys(arguments[i], newElement.type); - } - validatePropTypes(newElement); - return newElement; -} diff --git a/packages/react/src/ReactServer.experimental.js b/packages/react/src/ReactServer.experimental.js index 159391a28b262..ccc6de20eca34 100644 --- a/packages/react/src/ReactServer.experimental.js +++ b/packages/react/src/ReactServer.experimental.js @@ -22,7 +22,11 @@ import { REACT_SUSPENSE_TYPE, REACT_DEBUG_TRACING_MODE_TYPE, } from 'shared/ReactSymbols'; -import {cloneElement, createElement, isValidElement} from './ReactElement'; +import { + cloneElement, + createElement, + isValidElement, +} from './jsx/ReactJSXElement'; import {createRef} from './ReactCreateRef'; import { use, diff --git a/packages/react/src/ReactServer.js b/packages/react/src/ReactServer.js index 9715e0d94f8e1..cc7e2c1847e5a 100644 --- a/packages/react/src/ReactServer.js +++ b/packages/react/src/ReactServer.js @@ -21,7 +21,11 @@ import { REACT_STRICT_MODE_TYPE, REACT_SUSPENSE_TYPE, } from 'shared/ReactSymbols'; -import {cloneElement, createElement, isValidElement} from './ReactElement'; +import { + cloneElement, + createElement, + isValidElement, +} from './jsx/ReactJSXElement'; import {createRef} from './ReactCreateRef'; import {use, useId, useCallback, useDebugValue, useMemo} from './ReactHooks'; import {forwardRef} from './ReactForwardRef'; diff --git a/packages/react/src/jsx/ReactJSXElement.js b/packages/react/src/jsx/ReactJSXElement.js index d955dcf13222e..4e546963292a3 100644 --- a/packages/react/src/jsx/ReactJSXElement.js +++ b/packages/react/src/jsx/ReactJSXElement.js @@ -8,6 +8,7 @@ import getComponentNameFromType from 'shared/getComponentNameFromType'; import ReactSharedInternals from 'shared/ReactSharedInternals'; import hasOwnProperty from 'shared/hasOwnProperty'; +import assign from 'shared/assign'; import { getIteratorFn, REACT_ELEMENT_TYPE, @@ -512,6 +513,331 @@ export function jsxDEV(type, config, maybeKey, isStaticChildren, source, self) { } } +/** + * Create and return a new ReactElement of the given type. + * See https://reactjs.org/docs/react-api.html#createelement + */ +export function createElement(type, config, children) { + if (__DEV__) { + if (!isValidElementType(type)) { + // This is an invalid element type. + // + // We warn in this case but don't throw. We expect the element creation to + // succeed and there will likely be errors in render. + let info = ''; + if ( + type === undefined || + (typeof type === 'object' && + type !== null && + Object.keys(type).length === 0) + ) { + info += + ' You likely forgot to export your component from the file ' + + "it's defined in, or you might have mixed up default and named imports."; + } + + const sourceInfo = getSourceInfoErrorAddendumForProps(config); + if (sourceInfo) { + info += sourceInfo; + } else { + info += getDeclarationErrorAddendum(); + } + + let typeString; + if (type === null) { + typeString = 'null'; + } else if (isArray(type)) { + typeString = 'array'; + } else if (type !== undefined && type.$$typeof === REACT_ELEMENT_TYPE) { + typeString = `<${getComponentNameFromType(type.type) || 'Unknown'} />`; + info = + ' Did you accidentally export a JSX literal instead of a component?'; + } else { + typeString = typeof type; + } + + console.error( + 'React.createElement: type is invalid -- expected a string (for ' + + 'built-in components) or a class/function (for composite ' + + 'components) but got: %s.%s', + typeString, + info, + ); + } else { + // This is a valid element type. + + // Skip key warning if the type isn't valid since our key validation logic + // doesn't expect a non-string/function type and can throw confusing + // errors. We don't want exception behavior to differ between dev and + // prod. (Rendering will throw with a helpful message and as soon as the + // type is fixed, the key warnings will appear.) + for (let i = 2; i < arguments.length; i++) { + validateChildKeys(arguments[i], type); + } + } + + // Unlike the jsx() runtime, createElement() doesn't warn about key spread. + } + + let propName; + + // Reserved names are extracted + const props = {}; + + let key = null; + let ref = null; + + if (config != null) { + if (hasValidRef(config)) { + ref = config.ref; + + if (__DEV__) { + warnIfStringRefCannotBeAutoConverted(config, config.__self); + } + } + if (hasValidKey(config)) { + if (__DEV__) { + checkKeyStringCoercion(config.key); + } + key = '' + config.key; + } + + // Remaining properties are added to a new props object + for (propName in config) { + if ( + hasOwnProperty.call(config, propName) && + // Skip over reserved prop names + propName !== 'key' && + // TODO: `ref` will no longer be reserved in the next major + propName !== 'ref' && + // ...and maybe these, too, though we currently rely on them for + // warnings and debug information in dev. Need to decide if we're OK + // with dropping them. In the jsx() runtime it's not an issue because + // the data gets passed as separate arguments instead of props, but + // it would be nice to stop relying on them entirely so we can drop + // them from the internal Fiber field. + propName !== '__self' && + propName !== '__source' + ) { + props[propName] = config[propName]; + } + } + } + + // Children can be more than one argument, and those are transferred onto + // the newly allocated props object. + const childrenLength = arguments.length - 2; + if (childrenLength === 1) { + props.children = children; + } else if (childrenLength > 1) { + const childArray = Array(childrenLength); + for (let i = 0; i < childrenLength; i++) { + childArray[i] = arguments[i + 2]; + } + if (__DEV__) { + if (Object.freeze) { + Object.freeze(childArray); + } + } + props.children = childArray; + } + + // Resolve default props + if (type && type.defaultProps) { + const defaultProps = type.defaultProps; + for (propName in defaultProps) { + if (props[propName] === undefined) { + props[propName] = defaultProps[propName]; + } + } + } + if (__DEV__) { + if (key || ref) { + const displayName = + typeof type === 'function' + ? type.displayName || type.name || 'Unknown' + : type; + if (key) { + defineKeyPropWarningGetter(props, displayName); + } + if (ref) { + defineRefPropWarningGetter(props, displayName); + } + } + } + + const element = ReactElement( + type, + key, + ref, + undefined, + undefined, + ReactCurrentOwner.current, + props, + ); + + if (type === REACT_FRAGMENT_TYPE) { + validateFragmentProps(element); + } else { + validatePropTypes(element); + } + + return element; +} + +let didWarnAboutDeprecatedCreateFactory = false; + +/** + * Return a function that produces ReactElements of a given type. + * See https://reactjs.org/docs/react-api.html#createfactory + */ +export function createFactory(type) { + const factory = createElement.bind(null, type); + // Expose the type on the factory and the prototype so that it can be + // easily accessed on elements. E.g. `.type === Foo`. + // This should not be named `constructor` since this may not be the function + // that created the element, and it may not even be a constructor. + // Legacy hook: remove it + factory.type = type; + + if (__DEV__) { + if (!didWarnAboutDeprecatedCreateFactory) { + didWarnAboutDeprecatedCreateFactory = true; + console.warn( + 'React.createFactory() is deprecated and will be removed in ' + + 'a future major release. Consider using JSX ' + + 'or use React.createElement() directly instead.', + ); + } + // Legacy hook: remove it + Object.defineProperty(factory, 'type', { + enumerable: false, + get: function () { + console.warn( + 'Factory.type is deprecated. Access the class directly ' + + 'before passing it to createFactory.', + ); + Object.defineProperty(this, 'type', { + value: type, + }); + return type; + }, + }); + } + + return factory; +} + +export function cloneAndReplaceKey(oldElement, newKey) { + return ReactElement( + oldElement.type, + newKey, + oldElement.ref, + undefined, + undefined, + oldElement._owner, + oldElement.props, + ); +} + +/** + * Clone and return a new ReactElement using element as the starting point. + * See https://reactjs.org/docs/react-api.html#cloneelement + */ +export function cloneElement(element, config, children) { + if (element === null || element === undefined) { + throw new Error( + `React.cloneElement(...): The argument must be a React element, but you passed ${element}.`, + ); + } + + let propName; + + // Original props are copied + const props = assign({}, element.props); + + // Reserved names are extracted + let key = element.key; + let ref = element.ref; + + // Owner will be preserved, unless ref is overridden + let owner = element._owner; + + if (config != null) { + if (hasValidRef(config)) { + // Silently steal the ref from the parent. + ref = config.ref; + owner = ReactCurrentOwner.current; + } + if (hasValidKey(config)) { + if (__DEV__) { + checkKeyStringCoercion(config.key); + } + key = '' + config.key; + } + + // Remaining properties override existing props + let defaultProps; + if (element.type && element.type.defaultProps) { + defaultProps = element.type.defaultProps; + } + for (propName in config) { + if ( + hasOwnProperty.call(config, propName) && + // Skip over reserved prop names + propName !== 'key' && + // TODO: `ref` will no longer be reserved in the next major + propName !== 'ref' && + // ...and maybe these, too, though we currently rely on them for + // warnings and debug information in dev. Need to decide if we're OK + // with dropping them. In the jsx() runtime it's not an issue because + // the data gets passed as separate arguments instead of props, but + // it would be nice to stop relying on them entirely so we can drop + // them from the internal Fiber field. + propName !== '__self' && + propName !== '__source' + ) { + if (config[propName] === undefined && defaultProps !== undefined) { + // Resolve default props + props[propName] = defaultProps[propName]; + } else { + props[propName] = config[propName]; + } + } + } + } + + // Children can be more than one argument, and those are transferred onto + // the newly allocated props object. + const childrenLength = arguments.length - 2; + if (childrenLength === 1) { + props.children = children; + } else if (childrenLength > 1) { + const childArray = Array(childrenLength); + for (let i = 0; i < childrenLength; i++) { + childArray[i] = arguments[i + 2]; + } + props.children = childArray; + } + + const clonedElement = ReactElement( + element.type, + key, + ref, + undefined, + undefined, + owner, + props, + ); + + for (let i = 2; i < arguments.length; i++) { + validateChildKeys(arguments[i], clonedElement.type); + } + validatePropTypes(clonedElement); + + return clonedElement; +} + function getDeclarationErrorAddendum() { if (__DEV__) { if (ReactCurrentOwner.current) { @@ -524,6 +850,13 @@ function getDeclarationErrorAddendum() { } } +function getSourceInfoErrorAddendumForProps(elementProps) { + if (elementProps !== null && elementProps !== undefined) { + return getSourceInfoErrorAddendum(elementProps.__source); + } + return ''; +} + function getSourceInfoErrorAddendum(source) { if (__DEV__) { if (source !== undefined) { @@ -590,13 +923,11 @@ function validateChildKeys(node, parentType) { * @final */ export function isValidElement(object) { - if (__DEV__) { - return ( - typeof object === 'object' && - object !== null && - object.$$typeof === REACT_ELEMENT_TYPE - ); - } + return ( + typeof object === 'object' && + object !== null && + object.$$typeof === REACT_ELEMENT_TYPE + ); } const ownerHasKeyUseWarning = {};