-
Notifications
You must be signed in to change notification settings - Fork 47.3k
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
Add react-is package #12199
Changes from 9 commits
Commits
Show all changes
19 commits
Select commit
Hold shift + click to select a range
12d7a8c
Authoritative brand checking library
sebmarkbage f44c668
Patched up react-is and added some tests and docs
bvaughn 0f92bd3
Updated react-is version number to mirror other packages
bvaughn c91ef08
Fix export for tests
bvaughn 78c264d
Prettier
bvaughn cbffbd1
Add umd files to package
bvaughn 88d1dd4
typeOf returns symbols; symbols exported for switch/case
bvaughn b8f67c2
README tweaks
bvaughn 784fffc
Added broader isElement() tests
bvaughn 7b060d3
Replaced default exports with named exports
bvaughn 70bccd8
Added results.json
bvaughn b4ff653
Updated README
bvaughn 54588e4
Removed UMD target from react-is bundle
bvaughn 21a64bd
Documentation nits
bvaughn a06e6f7
Re-enabled UMD build for react-is
bvaughn b22a7b7
Made fragment/async/strict mode checks more strict
bvaughn ef0a900
Adjusted typeOf() to be more efficient
bvaughn f111111
Added a couple of additional tests
bvaughn 9c8385c
Removed unnecessary, nested not-null check
bvaughn File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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'); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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/" | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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.There was a problem hiding this comment.
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
)?There was a problem hiding this comment.
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:react/packages/react-call-return/index.js
Line 12 in 29e8924
If there's some issue with top-level ES import (maybe there was) then the above is sufficient.
There was a problem hiding this comment.
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.