Skip to content

Commit

Permalink
Add React.isValidElementType() (#12483)
Browse files Browse the repository at this point in the history
* Add React.isValidElementType()

Per the conversation on #12453, there are a number of third-party
libraries (particularly those that generate higher-order components)
that are performing suboptimal validation of element types.

This commit exposes a function that can perform the desired check
without depending upon React internals.

* Move isValidElementType to shared/
  • Loading branch information
jamesreggio authored and bvaughn committed Mar 29, 2018
1 parent 125dd16 commit 96fe3b1
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 21 deletions.
3 changes: 3 additions & 0 deletions packages/react-is/src/ReactIs.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
}
Expand Down
36 changes: 36 additions & 0 deletions packages/react-is/src/__tests__/ReactIs-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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(<React.unstable_AsyncMode />)).toBe(
ReactIs.AsyncMode,
Expand Down
24 changes: 3 additions & 21 deletions packages/react/src/ReactElementValidator.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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.
Expand Down
33 changes: 33 additions & 0 deletions packages/shared/isValidElementType.js
Original file line number Diff line number Diff line change
@@ -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))
);
}

0 comments on commit 96fe3b1

Please sign in to comment.