Skip to content

Commit

Permalink
Add react-is package (facebook#12199)
Browse files Browse the repository at this point in the history
Authoritative brand checking library.

Can be used without any dependency on React. Plausible replacement for `React.isValidElement.`
  • Loading branch information
bvaughn authored and rhagigi committed Apr 19, 2018
1 parent 40ae53c commit 401262d
Show file tree
Hide file tree
Showing 9 changed files with 353 additions and 0 deletions.
83 changes: 83 additions & 0 deletions packages/react-is/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# `react-is`

This package allows you to test arbitrary values and see if they're a particular React type, e.g. React Elements.

## Installation

```sh
# Yarn
yarn add react-is

# NPM
npm install react-is --save
```

## Usage

### AsyncMode

```js
import React from "react";
import * as ReactIs from 'react-is';

ReactIs.isAsyncMode(<React.unstable_AsyncMode />); // true
ReactIs.typeOf(<React.unstable_AsyncMode />) === ReactIs.AsyncMode; // true
```

### Context

```js
import React from "react";
import * as ReactIs from 'react-is';

const ThemeContext = React.createContext("blue");

ReactIs.isContextConsumer(<ThemeContext.Consumer />); // true
ReactIs.isContextProvider(<ThemeContext.Provider />); // true
ReactIs.typeOf(<ThemeContext.Provider />) === ReactIs.ContextProvider; // true
ReactIs.typeOf(<ThemeContext.Consumer />) === ReactIs.ContextConsumer; // true
```

### Element

```js
import React from "react";
import * as ReactIs from 'react-is';

ReactIs.isElement(<div />); // true
ReactIs.typeOf(<div />) === ReactIs.Element; // true
```

### Fragment

```js
import React from "react";
import * as ReactIs from 'react-is';

ReactIs.isFragment(<></>); // true
ReactIs.typeOf(<></>) === ReactIs.Fragment; // true
```

### Portal

```js
import React from "react";
import ReactDOM from "react-dom";
import * as ReactIs from 'react-is';

const div = document.createElement("div");
const portal = ReactDOM.createPortal(<div />, div);

ReactIs.isPortal(portal); // true
ReactIs.typeOf(portal) === ReactIs.Portal; // true
```

### StrictMode

```js
import React from "react";
import * as ReactIs from 'react-is';

ReactIs.isStrictMode(<React.StrictMode />); // true
ReactIs.typeOf(<React.StrictMode />) === ReactIs.StrictMode; // true
```
12 changes: 12 additions & 0 deletions packages/react-is/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
* Copyright (c) 2013-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
*/

'use strict';

export * from './src/ReactIs';
7 changes: 7 additions & 0 deletions packages/react-is/npm/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
'use strict';

if (process.env.NODE_ENV === 'production') {
module.exports = require('./cjs/react-is.production.min.js');
} else {
module.exports = require('./cjs/react-is.development.js');
}
23 changes: 23 additions & 0 deletions packages/react-is/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"name": "react-is",
"version": "16.3.0-alpha.0",
"description": "Brand checking of React Elements.",
"main": "index.js",
"repository": "facebook/react",
"keywords": ["react"],
"license": "MIT",
"bugs": {
"url": "https://github.com/facebook/react/issues"
},
"homepage": "https://reactjs.org/",
"peerDependencies": {
"react": "^16.0.0 || 16.3.0-alpha.0"
},
"files": [
"LICENSE",
"README.md",
"index.js",
"cjs/",
"umd/"
]
}
86 changes: 86 additions & 0 deletions packages/react-is/src/ReactIs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/**
* Copyright (c) 2013-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
*/

'use strict';

import {
REACT_ASYNC_MODE_TYPE,
REACT_CONTEXT_TYPE,
REACT_ELEMENT_TYPE,
REACT_FRAGMENT_TYPE,
REACT_PORTAL_TYPE,
REACT_PROVIDER_TYPE,
REACT_STRICT_MODE_TYPE,
} from 'shared/ReactSymbols';

export function typeOf(object: any) {
if (typeof object === 'object' && object !== null) {
const $$typeof = object.$$typeof;

switch ($$typeof) {
case REACT_ELEMENT_TYPE:
const type = object.type;

switch (type) {
case REACT_ASYNC_MODE_TYPE:
case REACT_FRAGMENT_TYPE:
case REACT_STRICT_MODE_TYPE:
return type;
default:
const $$typeofType = type.$$typeof;

switch ($$typeofType) {
case REACT_CONTEXT_TYPE:
case REACT_PROVIDER_TYPE:
return $$typeofType;
default:
return $$typeof;
}
}
case REACT_PORTAL_TYPE:
return $$typeof;
}
}

return undefined;
}

export const AsyncMode = REACT_ASYNC_MODE_TYPE;
export const ContextConsumer = REACT_CONTEXT_TYPE;
export const ContextProvider = REACT_PROVIDER_TYPE;
export const Element = REACT_ELEMENT_TYPE;
export const Fragment = REACT_FRAGMENT_TYPE;
export const Portal = REACT_PORTAL_TYPE;
export const StrictMode = REACT_STRICT_MODE_TYPE;

export function isAsyncMode(object: any) {
return typeOf(object) === REACT_ASYNC_MODE_TYPE;
}
export function isContextConsumer(object: any) {
return typeOf(object) === REACT_CONTEXT_TYPE;
}
export function isContextProvider(object: any) {
return typeOf(object) === REACT_PROVIDER_TYPE;
}
export function isElement(object: any) {
return (
typeof object === 'object' &&
object !== null &&
object.$$typeof === REACT_ELEMENT_TYPE
);
}
export function isFragment(object: any) {
return typeOf(object) === REACT_FRAGMENT_TYPE;
}
export function isPortal(object: any) {
return typeOf(object) === REACT_PORTAL_TYPE;
}
export function isStrictMode(object: any) {
return typeOf(object) === REACT_STRICT_MODE_TYPE;
}
103 changes: 103 additions & 0 deletions packages/react-is/src/__tests__/ReactIs-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/**
* Copyright (c) 2013-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.
*
* @emails react-core
*/

'use strict';

let React;
let ReactDOM;
let ReactIs;

describe('ReactIs', () => {
beforeEach(() => {
jest.resetModules();

React = require('react');
ReactDOM = require('react-dom');
ReactIs = require('react-is');
});

it('should return undefined for unknown/invalid types', () => {
expect(ReactIs.typeOf('abc')).toBe(undefined);
expect(ReactIs.typeOf(true)).toBe(undefined);
expect(ReactIs.typeOf(123)).toBe(undefined);
expect(ReactIs.typeOf({})).toBe(undefined);
expect(ReactIs.typeOf(null)).toBe(undefined);
expect(ReactIs.typeOf(undefined)).toBe(undefined);
});

it('should identify async mode', () => {
expect(ReactIs.typeOf(<React.unstable_AsyncMode />)).toBe(
ReactIs.AsyncMode,
);
expect(ReactIs.isAsyncMode(<React.unstable_AsyncMode />)).toBe(true);
expect(ReactIs.isAsyncMode({type: ReactIs.AsyncMode})).toBe(false);
expect(ReactIs.isAsyncMode(<React.StrictMode />)).toBe(false);
expect(ReactIs.isAsyncMode(<div />)).toBe(false);
});

it('should identify context consumers', () => {
const Context = React.createContext(false);
expect(ReactIs.typeOf(<Context.Consumer />)).toBe(ReactIs.ContextConsumer);
expect(ReactIs.isContextConsumer(<Context.Consumer />)).toBe(true);
expect(ReactIs.isContextConsumer(<Context.Provider />)).toBe(false);
expect(ReactIs.isContextConsumer(<div />)).toBe(false);
});

it('should identify context providers', () => {
const Context = React.createContext(false);
expect(ReactIs.typeOf(<Context.Provider />)).toBe(ReactIs.ContextProvider);
expect(ReactIs.isContextProvider(<Context.Provider />)).toBe(true);
expect(ReactIs.isContextProvider(<Context.Consumer />)).toBe(false);
expect(ReactIs.isContextProvider(<div />)).toBe(false);
});

it('should identify elements', () => {
expect(ReactIs.typeOf(<div />)).toBe(ReactIs.Element);
expect(ReactIs.isElement(<div />)).toBe(true);
expect(ReactIs.isElement('div')).toBe(false);
expect(ReactIs.isElement(true)).toBe(false);
expect(ReactIs.isElement(123)).toBe(false);
expect(ReactIs.isElement(null)).toBe(false);
expect(ReactIs.isElement(undefined)).toBe(false);
expect(ReactIs.isElement({})).toBe(false);

// It should also identify more specific types as elements
const Context = React.createContext(false);
expect(ReactIs.isElement(<Context.Provider />)).toBe(true);
expect(ReactIs.isElement(<Context.Consumer />)).toBe(true);
expect(ReactIs.isElement(<React.Fragment />)).toBe(true);
expect(ReactIs.isElement(<React.unstable_AsyncMode />)).toBe(true);
expect(ReactIs.isElement(<React.StrictMode />)).toBe(true);
});

it('should identify fragments', () => {
expect(ReactIs.typeOf(<React.Fragment />)).toBe(ReactIs.Fragment);
expect(ReactIs.isFragment(<React.Fragment />)).toBe(true);
expect(ReactIs.isFragment({type: ReactIs.Fragment})).toBe(false);
expect(ReactIs.isFragment('React.Fragment')).toBe(false);
expect(ReactIs.isFragment(<div />)).toBe(false);
expect(ReactIs.isFragment([])).toBe(false);
});

it('should identify portals', () => {
const div = document.createElement('div');
const portal = ReactDOM.createPortal(<div />, div);
expect(ReactIs.typeOf(portal)).toBe(ReactIs.Portal);
expect(ReactIs.isPortal(portal)).toBe(true);
expect(ReactIs.isPortal(div)).toBe(false);
});

it('should identify strict mode', () => {
expect(ReactIs.typeOf(<React.StrictMode />)).toBe(ReactIs.StrictMode);
expect(ReactIs.isStrictMode(<React.StrictMode />)).toBe(true);
expect(ReactIs.isStrictMode({type: ReactIs.StrictMode})).toBe(false);
expect(ReactIs.isStrictMode(<React.unstable_AsyncMode />)).toBe(false);
expect(ReactIs.isStrictMode(<div />)).toBe(false);
});
});
10 changes: 10 additions & 0 deletions scripts/rollup/bundles.js
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,16 @@ const bundles = [
global: 'ReactCallReturn',
externals: [],
},

/******* React Is *******/
{
label: 'react-is',
bundleTypes: [NODE_DEV, NODE_PROD, UMD_DEV, UMD_PROD],
moduleType: ISOMORPHIC,
entry: 'react-is',
global: 'ReactIs',
externals: [],
},
];

// Based on deep-freeze by substack (public domain)
Expand Down
28 changes: 28 additions & 0 deletions scripts/rollup/results.json
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,34 @@
"packageName": "react-reconciler",
"size": 41327,
"gzip": 13133
},
{
"filename": "react-is.development.js",
"bundleType": "NODE_DEV",
"packageName": "react-is",
"size": 3358,
"gzip": 1015
},
{
"filename": "react-is.production.min.js",
"bundleType": "NODE_PROD",
"packageName": "react-is",
"size": 1433,
"gzip": 607
},
{
"filename": "react-is.development.js",
"bundleType": "UMD_DEV",
"packageName": "react-is",
"size": 3547,
"gzip": 1071
},
{
"filename": "react-is.production.min.js",
"bundleType": "UMD_PROD",
"packageName": "react-is",
"size": 1515,
"gzip": 670
}
]
}
1 change: 1 addition & 0 deletions scripts/rollup/validate/eslintrc.umd.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ module.exports = {
// UMD wrapper code
// TODO: this is too permissive.
// Ideally we should only allow these *inside* the UMD wrapper.
exports: true,
module: true,
define: true,
require: true,
Expand Down

0 comments on commit 401262d

Please sign in to comment.