-
Notifications
You must be signed in to change notification settings - Fork 50k
Add react-is package #12199
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
Changes from 16 commits
12d7a8c
f44c668
0f92bd3
c91ef08
78c264d
cbffbd1
88d1dd4
b8f67c2
784fffc
7b060d3
70bccd8
b4ff653
54588e4
21a64bd
a06e6f7
b22a7b7
ef0a900
f111111
9c8385c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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 | ||
| ``` |
| 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'; |
| 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'); | ||
| } |
| 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/" | ||
| ] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,105 @@ | ||
| /** | ||
| * 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; | ||
| } | ||
|
|
||
| export function typeOf(object: any) { | ||
| let maybeType = getType(object); | ||
| switch (maybeType) { | ||
| case REACT_ASYNC_MODE_TYPE: | ||
| case REACT_FRAGMENT_TYPE: | ||
| case REACT_STRICT_MODE_TYPE: | ||
| if (getTypeOf(object) === REACT_ELEMENT_TYPE) { | ||
| return maybeType; | ||
| } | ||
| } | ||
|
|
||
| maybeType = getTypeTypeOf(object); | ||
| switch (maybeType) { | ||
| case REACT_CONTEXT_TYPE: | ||
| case REACT_PROVIDER_TYPE: | ||
| return maybeType; | ||
| } | ||
|
|
||
| maybeType = getTypeOf(object); | ||
| switch (maybeType) { | ||
| case REACT_ELEMENT_TYPE: | ||
| case REACT_PORTAL_TYPE: | ||
| return maybeType; | ||
| } | ||
| } | ||
|
|
||
| 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 ( | ||
|
||
| getType(object) === REACT_ASYNC_MODE_TYPE && | ||
| getTypeOf(object) === REACT_ELEMENT_TYPE | ||
| ); | ||
| } | ||
| export function isContextConsumer(object: any) { | ||
| return getTypeTypeOf(object) === REACT_CONTEXT_TYPE; | ||
| } | ||
| export function isContextProvider(object: any) { | ||
| return getTypeTypeOf(object) === REACT_PROVIDER_TYPE; | ||
| } | ||
| export function isElement(object: any) { | ||
| return getTypeOf(object) === REACT_ELEMENT_TYPE; | ||
| } | ||
| export function isFragment(object: any) { | ||
| return ( | ||
| getType(object) === REACT_FRAGMENT_TYPE && | ||
| getTypeOf(object) === REACT_ELEMENT_TYPE | ||
| ); | ||
| } | ||
| export function isPortal(object: any) { | ||
| return getTypeOf(object) === REACT_PORTAL_TYPE; | ||
| } | ||
| export function isStrictMode(object: any) { | ||
| return ( | ||
| getType(object) === REACT_STRICT_MODE_TYPE && | ||
| getTypeOf(object) === REACT_ELEMENT_TYPE | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,90 @@ | ||
| /** | ||
| * 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({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); | ||
|
|
||
| // 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); | ||
| }); | ||
| }); |
Uh oh!
There was an error while loading. Please reload this page.
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 find the structure of this function a bit hard to follow. Is there any specific reason it is written this way?
It also seems like we're reading
.typeand compare it many times in pretty common cases like when the input is an element.I would prefer to write it inline:
What do you think? IMO this makes the object structure a bit clearer, and avoids unnecessary property reads.
It also mirrors how we use it in practice (both Fiber and SSR) so it is helpful as a guide for "how do I handle all the cases".