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 9 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
80 changes: 80 additions & 0 deletions packages/react-is/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# `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 ReactIs from 'react-is';

const AsyncMode = React.unstable_AsyncMode;

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

### Context
```js
import React from 'react';
ReactIs 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 ReactIs from 'react-is';

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

### Fragment
```js
import React from 'react';
import ReactIs from 'react-is';

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

### Portal
```js
import React from 'react';
import ReactDOM from 'react-dom';
import 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 ReactIs from 'react-is';

const {StrictMode} = React;

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

const ReactIs = require('./src/ReactIs');

// TODO: decide on the top-level export form.
// This is hacky but makes it work with both Rollup and Jest.
module.exports = ReactIs.default ? ReactIs.default : ReactIs;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should do this correctly right away since it's not a legacy package.

Let's remove the default export here and only provide named exports. That's how we make it work with tree shaking, too (when we provide an ES build output).

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To clarify, I'm saying this file shouldn't need to use CommonJS at all. Just re-export * from the source.

Copy link
Contributor Author

@bvaughn bvaughn Feb 11, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm. Okay.

I'm curious: Why didn't we follow that pattern with other new packages (like react-reconciler)?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there was some reason but I don't remember straight away.

It's not very important in case of the reconciler though because it provides just one function. react-is, on the other hand, provides a bunch, and is expected to grow.

Call/Return does avoid the .default hack:

module.exports = require('./src/ReactCallReturn');

If there's some issue with top-level ES import (maybe there was) then the above is sufficient.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay. This makes sense. Thanks for the suggestion. 😄

It's been updated.

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/"
]
}
98 changes: 98 additions & 0 deletions packages/react-is/src/ReactIs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/**
* 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';

// e.g. Fragments, StrictMode
function getType(object: any) {
return typeof object === 'object' && object !== null ? object.type : null;
}

// e.g. Elements, Portals
function getTypeOf(object: any) {
return typeof object === 'object' && object !== null ? object.$$typeof : null;
}

// e.g. Context provider and consumer
function getTypeTypeOf(object: any) {
return typeof object === 'object' &&
object !== null &&
typeof object.type === 'object' &&
object.type !== null
? object.type.$$typeof
: null;
}

const ReactIs = {
typeOf(object: any) {
const type = getType(object);
switch (type) {
case REACT_ASYNC_MODE_TYPE:
case REACT_FRAGMENT_TYPE:
case REACT_STRICT_MODE_TYPE:
return type;
}

const typeTypeOf = getTypeTypeOf(object);
switch (typeTypeOf) {
case REACT_CONTEXT_TYPE:
case REACT_PROVIDER_TYPE:
return typeTypeOf;
}

const typeOf = getTypeOf(object);
switch (typeOf) {
case REACT_ELEMENT_TYPE:
case REACT_PORTAL_TYPE:
return typeOf;
}
},

AsyncMode: REACT_ASYNC_MODE_TYPE,
ContextConsumer: REACT_CONTEXT_TYPE,
ContextProvider: REACT_PROVIDER_TYPE,
Element: REACT_ELEMENT_TYPE,
Fragment: REACT_FRAGMENT_TYPE,
Portal: REACT_PORTAL_TYPE,
StrictMode: REACT_STRICT_MODE_TYPE,

isAsyncMode(object: any) {
return getType(object) === REACT_ASYNC_MODE_TYPE;
},
isContextConsumer(object: any) {
return getTypeTypeOf(object) === REACT_CONTEXT_TYPE;
},
isContextProvider(object: any) {
return getTypeTypeOf(object) === REACT_PROVIDER_TYPE;
},
isElement(object: any) {
return getTypeOf(object) === REACT_ELEMENT_TYPE;
},
isFragment(object: any) {
return getType(object) === REACT_FRAGMENT_TYPE;
},
isPortal(object: any) {
return getTypeOf(object) === REACT_PORTAL_TYPE;
},
isStrictMode(object: any) {
return getType(object) === REACT_STRICT_MODE_TYPE;
},
};

export default ReactIs;
87 changes: 87 additions & 0 deletions packages/react-is/src/__tests__/ReactIs-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/**
* 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 identify async mode', () => {
expect(ReactIs.typeOf(<React.unstable_AsyncMode />)).toBe(
ReactIs.AsyncMode,
);
expect(ReactIs.isAsyncMode(<React.unstable_AsyncMode />)).toBe(true);
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);

// 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('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(<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: [UMD_DEV, UMD_PROD, NODE_DEV, NODE_PROD],
moduleType: ISOMORPHIC,
entry: 'react-is',
global: 'ReactIs',
externals: [],
},
];

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