diff --git a/packages/react-is/src/ReactIs.js b/packages/react-is/src/ReactIs.js index cee716e0bfffe..4418759a9b233 100644 --- a/packages/react-is/src/ReactIs.js +++ b/packages/react-is/src/ReactIs.js @@ -19,6 +19,7 @@ import { REACT_PROVIDER_TYPE, REACT_STRICT_MODE_TYPE, } from 'shared/ReactSymbols'; +import isValidElementType from 'shared/isValidElementType'; export function typeOf(object: any) { if (typeof object === 'object' && object !== null) { @@ -62,6 +63,8 @@ export const Fragment = REACT_FRAGMENT_TYPE; export const Portal = REACT_PORTAL_TYPE; export const StrictMode = REACT_STRICT_MODE_TYPE; +export {isValidElementType}; + export function isAsyncMode(object: any) { return typeOf(object) === REACT_ASYNC_MODE_TYPE; } diff --git a/packages/react-is/src/__tests__/ReactIs-test.js b/packages/react-is/src/__tests__/ReactIs-test.js index ab7f6e0d4c24f..e4ce5074b1a93 100644 --- a/packages/react-is/src/__tests__/ReactIs-test.js +++ b/packages/react-is/src/__tests__/ReactIs-test.js @@ -31,6 +31,42 @@ describe('ReactIs', () => { expect(ReactIs.typeOf(undefined)).toBe(undefined); }); + it('identifies valid element types', () => { + class Component extends React.Component { + render() { + return React.createElement('div'); + } + } + + const StatelessComponent = () => React.createElement('div'); + + const ForwardRefComponent = React.forwardRef((props, ref) => + React.createElement(Component, {forwardedRef: ref, ...props}), + ); + + const Context = React.createContext(false); + + expect(ReactIs.isValidElementType('div')).toEqual(true); + expect(ReactIs.isValidElementType(Component)).toEqual(true); + expect(ReactIs.isValidElementType(StatelessComponent)).toEqual(true); + expect(ReactIs.isValidElementType(ForwardRefComponent)).toEqual(true); + expect(ReactIs.isValidElementType(Context.Provider)).toEqual(true); + expect(ReactIs.isValidElementType(Context.Consumer)).toEqual(true); + expect(ReactIs.isValidElementType(React.createFactory('div'))).toEqual( + true, + ); + expect(ReactIs.isValidElementType(React.Fragment)).toEqual(true); + expect(ReactIs.isValidElementType(React.unstable_AsyncMode)).toEqual(true); + expect(ReactIs.isValidElementType(React.StrictMode)).toEqual(true); + + expect(ReactIs.isValidElementType(true)).toEqual(false); + expect(ReactIs.isValidElementType(123)).toEqual(false); + expect(ReactIs.isValidElementType({})).toEqual(false); + expect(ReactIs.isValidElementType(null)).toEqual(false); + expect(ReactIs.isValidElementType(undefined)).toEqual(false); + expect(ReactIs.isValidElementType({type: 'div', props: {}})).toEqual(false); + }); + it('should identify async mode', () => { expect(ReactIs.typeOf()).toBe( ReactIs.AsyncMode, diff --git a/packages/react/src/ReactElementValidator.js b/packages/react/src/ReactElementValidator.js index fff09423d3e04..c7fdad83ed1ed 100644 --- a/packages/react/src/ReactElementValidator.js +++ b/packages/react/src/ReactElementValidator.js @@ -14,16 +14,9 @@ import lowPriorityWarning from 'shared/lowPriorityWarning'; import describeComponentFrame from 'shared/describeComponentFrame'; +import isValidElementType from 'shared/isValidElementType'; import getComponentName from 'shared/getComponentName'; -import { - getIteratorFn, - REACT_FRAGMENT_TYPE, - REACT_STRICT_MODE_TYPE, - REACT_ASYNC_MODE_TYPE, - REACT_PROVIDER_TYPE, - REACT_CONTEXT_TYPE, - REACT_FORWARD_REF_TYPE, -} from 'shared/ReactSymbols'; +import {getIteratorFn, REACT_FRAGMENT_TYPE} from 'shared/ReactSymbols'; import checkPropTypes from 'prop-types/checkPropTypes'; import warning from 'fbjs/lib/warning'; @@ -288,18 +281,7 @@ function validateFragmentProps(fragment) { } export function createElementWithValidation(type, props, children) { - const validType = - typeof type === 'string' || - typeof type === 'function' || - // Note: its typeof might be other than 'symbol' or 'number' if it's a polyfill. - type === REACT_FRAGMENT_TYPE || - type === REACT_ASYNC_MODE_TYPE || - type === REACT_STRICT_MODE_TYPE || - (typeof type === 'object' && - type !== null && - (type.$$typeof === REACT_PROVIDER_TYPE || - type.$$typeof === REACT_CONTEXT_TYPE || - type.$$typeof === REACT_FORWARD_REF_TYPE)); + 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. diff --git a/packages/shared/isValidElementType.js b/packages/shared/isValidElementType.js new file mode 100644 index 0000000000000..7a333c8c11ac4 --- /dev/null +++ b/packages/shared/isValidElementType.js @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +import { + REACT_FRAGMENT_TYPE, + REACT_ASYNC_MODE_TYPE, + REACT_STRICT_MODE_TYPE, + REACT_PROVIDER_TYPE, + REACT_CONTEXT_TYPE, + REACT_FORWARD_REF_TYPE, +} from 'shared/ReactSymbols'; + +export default function isValidElementType(type: mixed) { + return ( + typeof type === 'string' || + typeof type === 'function' || + // Note: its typeof might be other than 'symbol' or 'number' if it's a polyfill. + type === REACT_FRAGMENT_TYPE || + type === REACT_ASYNC_MODE_TYPE || + type === REACT_STRICT_MODE_TYPE || + (typeof type === 'object' && + type !== null && + (type.$$typeof === REACT_PROVIDER_TYPE || + type.$$typeof === REACT_CONTEXT_TYPE || + type.$$typeof === REACT_FORWARD_REF_TYPE)) + ); +}