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/
James Reggio committed
Mar 29, 2018 at 14:45 UTC
96fe3b1be2fe74e83c9a25d7511f23dbef15ac99
4 files changed
+75
-21
packages/react-is/src/ReactIs.js
+3
@@ -19,6 +19,7 @@ import {
19
REACT_PROVIDER_TYPE,
20
REACT_STRICT_MODE_TYPE,
21
} from 'shared/ReactSymbols';
22
+import isValidElementType from 'shared/isValidElementType';
23
24
export function typeOf(object: any) {
25
if (typeof object === 'object' && object !== null) {
@@ -62,6 +63,8 @@ export const Fragment = REACT_FRAGMENT_TYPE;
63
export const Portal = REACT_PORTAL_TYPE;
64
export const StrictMode = REACT_STRICT_MODE_TYPE;
65
66
+export {isValidElementType};
67
+
68
export function isAsyncMode(object: any) {
69
return typeOf(object) === REACT_ASYNC_MODE_TYPE;
70
}
packages/react-is/src/__tests__/ReactIs-test.js
+36
@@ -31,6 +31,42 @@ describe('ReactIs', () => {
31
expect(ReactIs.typeOf(undefined)).toBe(undefined);
32
});
33
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
+
70
it('should identify async mode', () => {
71
expect(ReactIs.typeOf(<React.unstable_AsyncMode />)).toBe(
72
ReactIs.AsyncMode,
packages/react/src/ReactElementValidator.js
+3
-21
@@ -14,16 +14,9 @@
14
15
import lowPriorityWarning from 'shared/lowPriorityWarning';
16
import describeComponentFrame from 'shared/describeComponentFrame';
17
+import isValidElementType from 'shared/isValidElementType';
18
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';
20
import checkPropTypes from 'prop-types/checkPropTypes';
21
import warning from 'fbjs/lib/warning';
22
@@ -288,18 +281,7 @@ function validateFragmentProps(fragment) {
281
}
282
283
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);
285
286
// We warn in this case but don't throw. We expect the element creation to
287
// succeed and there will likely be errors in render.
packages/shared/isValidElementType.js
new
+33
@@ -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
+}