Add react-is package (#12199)
Authoritative brand checking library. Can be used without any dependency on React. Plausible replacement for `React.isValidElement.`
Brian Vaughn committed
Feb 11, 2018 at 14:08 UTC
41b8c65f1e81ba48192fbb65fd5c742638ef4f82
9 files changed
+353
packages/react-is/README.md
new
+83
@@ -0,0 +1,83 @@
1
+# `react-is`
2
+
3
+This package allows you to test arbitrary values and see if they're a particular React type, e.g. React Elements.
4
+
5
+## Installation
6
+
7
+```sh
8
+# Yarn
9
+yarn add react-is
10
+
11
+# NPM
12
+npm install react-is --save
13
+```
14
+
15
+## Usage
16
+
17
+### AsyncMode
18
+
19
+```js
20
+import React from "react";
21
+import * as ReactIs from 'react-is';
22
+
23
+ReactIs.isAsyncMode(<React.unstable_AsyncMode />); // true
24
+ReactIs.typeOf(<React.unstable_AsyncMode />) === ReactIs.AsyncMode; // true
25
+```
26
+
27
+### Context
28
+
29
+```js
30
+import React from "react";
31
+import * as ReactIs from 'react-is';
32
+
33
+const ThemeContext = React.createContext("blue");
34
+
35
+ReactIs.isContextConsumer(<ThemeContext.Consumer />); // true
36
+ReactIs.isContextProvider(<ThemeContext.Provider />); // true
37
+ReactIs.typeOf(<ThemeContext.Provider />) === ReactIs.ContextProvider; // true
38
+ReactIs.typeOf(<ThemeContext.Consumer />) === ReactIs.ContextConsumer; // true
39
+```
40
+
41
+### Element
42
+
43
+```js
44
+import React from "react";
45
+import * as ReactIs from 'react-is';
46
+
47
+ReactIs.isElement(<div />); // true
48
+ReactIs.typeOf(<div />) === ReactIs.Element; // true
49
+```
50
+
51
+### Fragment
52
+
53
+```js
54
+import React from "react";
55
+import * as ReactIs from 'react-is';
56
+
57
+ReactIs.isFragment(<></>); // true
58
+ReactIs.typeOf(<></>) === ReactIs.Fragment; // true
59
+```
60
+
61
+### Portal
62
+
63
+```js
64
+import React from "react";
65
+import ReactDOM from "react-dom";
66
+import * as ReactIs from 'react-is';
67
+
68
+const div = document.createElement("div");
69
+const portal = ReactDOM.createPortal(<div />, div);
70
+
71
+ReactIs.isPortal(portal); // true
72
+ReactIs.typeOf(portal) === ReactIs.Portal; // true
73
+```
74
+
75
+### StrictMode
76
+
77
+```js
78
+import React from "react";
79
+import * as ReactIs from 'react-is';
80
+
81
+ReactIs.isStrictMode(<React.StrictMode />); // true
82
+ReactIs.typeOf(<React.StrictMode />) === ReactIs.StrictMode; // true
83
+```
packages/react-is/index.js
new
+12
@@ -0,0 +1,12 @@
1
+/**
2
+ * Copyright (c) 2013-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
+'use strict';
11
+
12
+export * from './src/ReactIs';
packages/react-is/npm/index.js
new
+7
@@ -0,0 +1,7 @@
1
+'use strict';
2
+
3
+if (process.env.NODE_ENV === 'production') {
4
+ module.exports = require('./cjs/react-is.production.min.js');
5
+} else {
6
+ module.exports = require('./cjs/react-is.development.js');
7
+}
packages/react-is/package.json
new
+23
@@ -0,0 +1,23 @@
1
+{
2
+ "name": "react-is",
3
+ "version": "16.3.0-alpha.0",
4
+ "description": "Brand checking of React Elements.",
5
+ "main": "index.js",
6
+ "repository": "facebook/react",
7
+ "keywords": ["react"],
8
+ "license": "MIT",
9
+ "bugs": {
10
+ "url": "https://github.com/facebook/react/issues"
11
+ },
12
+ "homepage": "https://reactjs.org/",
13
+ "peerDependencies": {
14
+ "react": "^16.0.0 || 16.3.0-alpha.0"
15
+ },
16
+ "files": [
17
+ "LICENSE",
18
+ "README.md",
19
+ "index.js",
20
+ "cjs/",
21
+ "umd/"
22
+ ]
23
+}
\ No newline at end of file
packages/react-is/src/ReactIs.js
new
+86
@@ -0,0 +1,86 @@
1
+/**
2
+ * Copyright (c) 2013-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
+'use strict';
11
+
12
+import {
13
+ REACT_ASYNC_MODE_TYPE,
14
+ REACT_CONTEXT_TYPE,
15
+ REACT_ELEMENT_TYPE,
16
+ REACT_FRAGMENT_TYPE,
17
+ REACT_PORTAL_TYPE,
18
+ REACT_PROVIDER_TYPE,
19
+ REACT_STRICT_MODE_TYPE,
20
+} from 'shared/ReactSymbols';
21
+
22
+export function typeOf(object: any) {
23
+ if (typeof object === 'object' && object !== null) {
24
+ const $$typeof = object.$$typeof;
25
+
26
+ switch ($$typeof) {
27
+ case REACT_ELEMENT_TYPE:
28
+ const type = object.type;
29
+
30
+ switch (type) {
31
+ case REACT_ASYNC_MODE_TYPE:
32
+ case REACT_FRAGMENT_TYPE:
33
+ case REACT_STRICT_MODE_TYPE:
34
+ return type;
35
+ default:
36
+ const $$typeofType = type.$$typeof;
37
+
38
+ switch ($$typeofType) {
39
+ case REACT_CONTEXT_TYPE:
40
+ case REACT_PROVIDER_TYPE:
41
+ return $$typeofType;
42
+ default:
43
+ return $$typeof;
44
+ }
45
+ }
46
+ case REACT_PORTAL_TYPE:
47
+ return $$typeof;
48
+ }
49
+ }
50
+
51
+ return undefined;
52
+}
53
+
54
+export const AsyncMode = REACT_ASYNC_MODE_TYPE;
55
+export const ContextConsumer = REACT_CONTEXT_TYPE;
56
+export const ContextProvider = REACT_PROVIDER_TYPE;
57
+export const Element = REACT_ELEMENT_TYPE;
58
+export const Fragment = REACT_FRAGMENT_TYPE;
59
+export const Portal = REACT_PORTAL_TYPE;
60
+export const StrictMode = REACT_STRICT_MODE_TYPE;
61
+
62
+export function isAsyncMode(object: any) {
63
+ return typeOf(object) === REACT_ASYNC_MODE_TYPE;
64
+}
65
+export function isContextConsumer(object: any) {
66
+ return typeOf(object) === REACT_CONTEXT_TYPE;
67
+}
68
+export function isContextProvider(object: any) {
69
+ return typeOf(object) === REACT_PROVIDER_TYPE;
70
+}
71
+export function isElement(object: any) {
72
+ return (
73
+ typeof object === 'object' &&
74
+ object !== null &&
75
+ object.$$typeof === REACT_ELEMENT_TYPE
76
+ );
77
+}
78
+export function isFragment(object: any) {
79
+ return typeOf(object) === REACT_FRAGMENT_TYPE;
80
+}
81
+export function isPortal(object: any) {
82
+ return typeOf(object) === REACT_PORTAL_TYPE;
83
+}
84
+export function isStrictMode(object: any) {
85
+ return typeOf(object) === REACT_STRICT_MODE_TYPE;
86
+}
packages/react-is/src/__tests__/ReactIs-test.js
new
+103
@@ -0,0 +1,103 @@
1
+/**
2
+ * Copyright (c) 2013-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
+ * @emails react-core
8
+ */
9
+
10
+'use strict';
11
+
12
+let React;
13
+let ReactDOM;
14
+let ReactIs;
15
+
16
+describe('ReactIs', () => {
17
+ beforeEach(() => {
18
+ jest.resetModules();
19
+
20
+ React = require('react');
21
+ ReactDOM = require('react-dom');
22
+ ReactIs = require('react-is');
23
+ });
24
+
25
+ it('should return undefined for unknown/invalid types', () => {
26
+ expect(ReactIs.typeOf('abc')).toBe(undefined);
27
+ expect(ReactIs.typeOf(true)).toBe(undefined);
28
+ expect(ReactIs.typeOf(123)).toBe(undefined);
29
+ expect(ReactIs.typeOf({})).toBe(undefined);
30
+ expect(ReactIs.typeOf(null)).toBe(undefined);
31
+ expect(ReactIs.typeOf(undefined)).toBe(undefined);
32
+ });
33
+
34
+ it('should identify async mode', () => {
35
+ expect(ReactIs.typeOf(<React.unstable_AsyncMode />)).toBe(
36
+ ReactIs.AsyncMode,
37
+ );
38
+ expect(ReactIs.isAsyncMode(<React.unstable_AsyncMode />)).toBe(true);
39
+ expect(ReactIs.isAsyncMode({type: ReactIs.AsyncMode})).toBe(false);
40
+ expect(ReactIs.isAsyncMode(<React.StrictMode />)).toBe(false);
41
+ expect(ReactIs.isAsyncMode(<div />)).toBe(false);
42
+ });
43
+
44
+ it('should identify context consumers', () => {
45
+ const Context = React.createContext(false);
46
+ expect(ReactIs.typeOf(<Context.Consumer />)).toBe(ReactIs.ContextConsumer);
47
+ expect(ReactIs.isContextConsumer(<Context.Consumer />)).toBe(true);
48
+ expect(ReactIs.isContextConsumer(<Context.Provider />)).toBe(false);
49
+ expect(ReactIs.isContextConsumer(<div />)).toBe(false);
50
+ });
51
+
52
+ it('should identify context providers', () => {
53
+ const Context = React.createContext(false);
54
+ expect(ReactIs.typeOf(<Context.Provider />)).toBe(ReactIs.ContextProvider);
55
+ expect(ReactIs.isContextProvider(<Context.Provider />)).toBe(true);
56
+ expect(ReactIs.isContextProvider(<Context.Consumer />)).toBe(false);
57
+ expect(ReactIs.isContextProvider(<div />)).toBe(false);
58
+ });
59
+
60
+ it('should identify elements', () => {
61
+ expect(ReactIs.typeOf(<div />)).toBe(ReactIs.Element);
62
+ expect(ReactIs.isElement(<div />)).toBe(true);
63
+ expect(ReactIs.isElement('div')).toBe(false);
64
+ expect(ReactIs.isElement(true)).toBe(false);
65
+ expect(ReactIs.isElement(123)).toBe(false);
66
+ expect(ReactIs.isElement(null)).toBe(false);
67
+ expect(ReactIs.isElement(undefined)).toBe(false);
68
+ expect(ReactIs.isElement({})).toBe(false);
69
+
70
+ // It should also identify more specific types as elements
71
+ const Context = React.createContext(false);
72
+ expect(ReactIs.isElement(<Context.Provider />)).toBe(true);
73
+ expect(ReactIs.isElement(<Context.Consumer />)).toBe(true);
74
+ expect(ReactIs.isElement(<React.Fragment />)).toBe(true);
75
+ expect(ReactIs.isElement(<React.unstable_AsyncMode />)).toBe(true);
76
+ expect(ReactIs.isElement(<React.StrictMode />)).toBe(true);
77
+ });
78
+
79
+ it('should identify fragments', () => {
80
+ expect(ReactIs.typeOf(<React.Fragment />)).toBe(ReactIs.Fragment);
81
+ expect(ReactIs.isFragment(<React.Fragment />)).toBe(true);
82
+ expect(ReactIs.isFragment({type: ReactIs.Fragment})).toBe(false);
83
+ expect(ReactIs.isFragment('React.Fragment')).toBe(false);
84
+ expect(ReactIs.isFragment(<div />)).toBe(false);
85
+ expect(ReactIs.isFragment([])).toBe(false);
86
+ });
87
+
88
+ it('should identify portals', () => {
89
+ const div = document.createElement('div');
90
+ const portal = ReactDOM.createPortal(<div />, div);
91
+ expect(ReactIs.typeOf(portal)).toBe(ReactIs.Portal);
92
+ expect(ReactIs.isPortal(portal)).toBe(true);
93
+ expect(ReactIs.isPortal(div)).toBe(false);
94
+ });
95
+
96
+ it('should identify strict mode', () => {
97
+ expect(ReactIs.typeOf(<React.StrictMode />)).toBe(ReactIs.StrictMode);
98
+ expect(ReactIs.isStrictMode(<React.StrictMode />)).toBe(true);
99
+ expect(ReactIs.isStrictMode({type: ReactIs.StrictMode})).toBe(false);
100
+ expect(ReactIs.isStrictMode(<React.unstable_AsyncMode />)).toBe(false);
101
+ expect(ReactIs.isStrictMode(<div />)).toBe(false);
102
+ });
103
+});
scripts/rollup/bundles.js
+10
@@ -234,6 +234,16 @@ const bundles = [
234
global: 'ReactCallReturn',
235
externals: [],
236
},
237
+
238
+ /******* React Is *******/
239
+ {
240
+ label: 'react-is',
241
+ bundleTypes: [NODE_DEV, NODE_PROD, UMD_DEV, UMD_PROD],
242
+ moduleType: ISOMORPHIC,
243
+ entry: 'react-is',
244
+ global: 'ReactIs',
245
+ externals: [],
246
+ },
247
];
248
249
// Based on deep-freeze by substack (public domain)
scripts/rollup/results.json
+28
@@ -398,6 +398,34 @@
398
"packageName": "react-reconciler",
399
"size": 41327,
400
"gzip": 13133
401
+ },
402
+ {
403
+ "filename": "react-is.development.js",
404
+ "bundleType": "NODE_DEV",
405
+ "packageName": "react-is",
406
+ "size": 3358,
407
+ "gzip": 1015
408
+ },
409
+ {
410
+ "filename": "react-is.production.min.js",
411
+ "bundleType": "NODE_PROD",
412
+ "packageName": "react-is",
413
+ "size": 1433,
414
+ "gzip": 607
415
+ },
416
+ {
417
+ "filename": "react-is.development.js",
418
+ "bundleType": "UMD_DEV",
419
+ "packageName": "react-is",
420
+ "size": 3547,
421
+ "gzip": 1071
422
+ },
423
+ {
424
+ "filename": "react-is.production.min.js",
425
+ "bundleType": "UMD_PROD",
426
+ "packageName": "react-is",
427
+ "size": 1515,
428
+ "gzip": 670
429
}
430
]
431
}
\ No newline at end of file
scripts/rollup/validate/eslintrc.umd.js
+1
@@ -17,6 +17,7 @@ module.exports = {
17
// UMD wrapper code
18
// TODO: this is too permissive.
19
// Ideally we should only allow these *inside* the UMD wrapper.
20
+ exports: true,
21
module: true,
22
define: true,
23
require: true,