Skip to content

Commit 401262d

Browse files
bvaughnrhagigi
authored andcommitted
Add react-is package (facebook#12199)
Authoritative brand checking library. Can be used without any dependency on React. Plausible replacement for `React.isValidElement.`
1 parent 40ae53c commit 401262d

File tree

9 files changed

+353
-0
lines changed

9 files changed

+353
-0
lines changed

packages/react-is/README.md

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
}

packages/react-is/src/ReactIs.js

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,16 @@ const bundles = [
234234
global: 'ReactCallReturn',
235235
externals: [],
236236
},
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+
},
237247
];
238248

239249
// Based on deep-freeze by substack (public domain)

scripts/rollup/results.json

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,34 @@
398398
"packageName": "react-reconciler",
399399
"size": 41327,
400400
"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
401429
}
402430
]
403431
}

scripts/rollup/validate/eslintrc.umd.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ module.exports = {
1717
// UMD wrapper code
1818
// TODO: this is too permissive.
1919
// Ideally we should only allow these *inside* the UMD wrapper.
20+
exports: true,
2021
module: true,
2122
define: true,
2223
require: true,

0 commit comments

Comments
 (0)