Skip to content

Commit 96fe3b1

Browse files
jamesreggiobvaughn
authored andcommitted
Add React.isValidElementType() (#12483)
* 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/
1 parent 125dd16 commit 96fe3b1

File tree

4 files changed

+75
-21
lines changed

4 files changed

+75
-21
lines changed

packages/react-is/src/ReactIs.js

+3
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
REACT_PROVIDER_TYPE,
2020
REACT_STRICT_MODE_TYPE,
2121
} from 'shared/ReactSymbols';
22+
import isValidElementType from 'shared/isValidElementType';
2223

2324
export function typeOf(object: any) {
2425
if (typeof object === 'object' && object !== null) {
@@ -62,6 +63,8 @@ export const Fragment = REACT_FRAGMENT_TYPE;
6263
export const Portal = REACT_PORTAL_TYPE;
6364
export const StrictMode = REACT_STRICT_MODE_TYPE;
6465

66+
export {isValidElementType};
67+
6568
export function isAsyncMode(object: any) {
6669
return typeOf(object) === REACT_ASYNC_MODE_TYPE;
6770
}

packages/react-is/src/__tests__/ReactIs-test.js

+36
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,42 @@ describe('ReactIs', () => {
3131
expect(ReactIs.typeOf(undefined)).toBe(undefined);
3232
});
3333

34+
it('identifies valid element types', () => {
35+
class Component extends React.Component {
36+
render() {
37+
return React.createElement('div');
38+
}
39+
}
40+
41+
const StatelessComponent = () => React.createElement('div');
42+
43+
const ForwardRefComponent = React.forwardRef((props, ref) =>
44+
React.createElement(Component, {forwardedRef: ref, ...props}),
45+
);
46+
47+
const Context = React.createContext(false);
48+
49+
expect(ReactIs.isValidElementType('div')).toEqual(true);
50+
expect(ReactIs.isValidElementType(Component)).toEqual(true);
51+
expect(ReactIs.isValidElementType(StatelessComponent)).toEqual(true);
52+
expect(ReactIs.isValidElementType(ForwardRefComponent)).toEqual(true);
53+
expect(ReactIs.isValidElementType(Context.Provider)).toEqual(true);
54+
expect(ReactIs.isValidElementType(Context.Consumer)).toEqual(true);
55+
expect(ReactIs.isValidElementType(React.createFactory('div'))).toEqual(
56+
true,
57+
);
58+
expect(ReactIs.isValidElementType(React.Fragment)).toEqual(true);
59+
expect(ReactIs.isValidElementType(React.unstable_AsyncMode)).toEqual(true);
60+
expect(ReactIs.isValidElementType(React.StrictMode)).toEqual(true);
61+
62+
expect(ReactIs.isValidElementType(true)).toEqual(false);
63+
expect(ReactIs.isValidElementType(123)).toEqual(false);
64+
expect(ReactIs.isValidElementType({})).toEqual(false);
65+
expect(ReactIs.isValidElementType(null)).toEqual(false);
66+
expect(ReactIs.isValidElementType(undefined)).toEqual(false);
67+
expect(ReactIs.isValidElementType({type: 'div', props: {}})).toEqual(false);
68+
});
69+
3470
it('should identify async mode', () => {
3571
expect(ReactIs.typeOf(<React.unstable_AsyncMode />)).toBe(
3672
ReactIs.AsyncMode,

packages/react/src/ReactElementValidator.js

+3-21
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,9 @@
1414

1515
import lowPriorityWarning from 'shared/lowPriorityWarning';
1616
import describeComponentFrame from 'shared/describeComponentFrame';
17+
import isValidElementType from 'shared/isValidElementType';
1718
import getComponentName from 'shared/getComponentName';
18-
import {
19-
getIteratorFn,
20-
REACT_FRAGMENT_TYPE,
21-
REACT_STRICT_MODE_TYPE,
22-
REACT_ASYNC_MODE_TYPE,
23-
REACT_PROVIDER_TYPE,
24-
REACT_CONTEXT_TYPE,
25-
REACT_FORWARD_REF_TYPE,
26-
} from 'shared/ReactSymbols';
19+
import {getIteratorFn, REACT_FRAGMENT_TYPE} from 'shared/ReactSymbols';
2720
import checkPropTypes from 'prop-types/checkPropTypes';
2821
import warning from 'fbjs/lib/warning';
2922

@@ -288,18 +281,7 @@ function validateFragmentProps(fragment) {
288281
}
289282

290283
export function createElementWithValidation(type, props, children) {
291-
const validType =
292-
typeof type === 'string' ||
293-
typeof type === 'function' ||
294-
// Note: its typeof might be other than 'symbol' or 'number' if it's a polyfill.
295-
type === REACT_FRAGMENT_TYPE ||
296-
type === REACT_ASYNC_MODE_TYPE ||
297-
type === REACT_STRICT_MODE_TYPE ||
298-
(typeof type === 'object' &&
299-
type !== null &&
300-
(type.$$typeof === REACT_PROVIDER_TYPE ||
301-
type.$$typeof === REACT_CONTEXT_TYPE ||
302-
type.$$typeof === REACT_FORWARD_REF_TYPE));
284+
const validType = isValidElementType(type);
303285

304286
// We warn in this case but don't throw. We expect the element creation to
305287
// succeed and there will likely be errors in render.

packages/shared/isValidElementType.js

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/**
2+
* Copyright (c) 2016-present, Facebook, Inc.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow
8+
*/
9+
10+
import {
11+
REACT_FRAGMENT_TYPE,
12+
REACT_ASYNC_MODE_TYPE,
13+
REACT_STRICT_MODE_TYPE,
14+
REACT_PROVIDER_TYPE,
15+
REACT_CONTEXT_TYPE,
16+
REACT_FORWARD_REF_TYPE,
17+
} from 'shared/ReactSymbols';
18+
19+
export default function isValidElementType(type: mixed) {
20+
return (
21+
typeof type === 'string' ||
22+
typeof type === 'function' ||
23+
// Note: its typeof might be other than 'symbol' or 'number' if it's a polyfill.
24+
type === REACT_FRAGMENT_TYPE ||
25+
type === REACT_ASYNC_MODE_TYPE ||
26+
type === REACT_STRICT_MODE_TYPE ||
27+
(typeof type === 'object' &&
28+
type !== null &&
29+
(type.$$typeof === REACT_PROVIDER_TYPE ||
30+
type.$$typeof === REACT_CONTEXT_TYPE ||
31+
type.$$typeof === REACT_FORWARD_REF_TYPE))
32+
);
33+
}

0 commit comments

Comments
 (0)