Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add react-is package #12199

Merged
merged 19 commits into from
Feb 11, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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