diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ce56e7e77896..3765106bc4ccc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,31 @@ +## 15.5.0 (April 7, 2017) + +### React + +* Added a deprecation warning for `React.createClass`. Points users to create-react-class instead. ([@acdlite](https://github.com/acdlite) in [d9a4fa4](https://github.com/facebook/react/commit/d9a4fa4f51c6da895e1655f32255cf72c0fe620e)) +* Added a deprecation warning for `React.PropTypes`. Points users to prop-types instead. ([@acdlite](https://github.com/acdlite) in [043845c](https://github.com/facebook/react/commit/043845ce75ea0812286bbbd9d34994bb7e01eb28)) +* Fixed an issue when using `ReactDOM` together with `ReactDOMServer`. ([@wacii](https://github.com/wacii) in [#9005](https://github.com/facebook/react/pull/9005)) +* Fixed issue with Closure Compiler. ([@anmonteiro](https://github.com/anmonteiro) in [#8895](https://github.com/facebook/react/pull/8895)) +* Another fix for Closure Compiler. ([@Shastel](https://github.com/Shastel) in [#8882](https://github.com/facebook/react/pull/8882)) +* Added component stack info to invalid element type warning. ([@n3tr](https://github.com/n3tr) in [#8495](https://github.com/facebook/react/pull/8495)) + +### React DOM + +* Fixed Chrome bug when backspacing in number inputs. ([@nhunzaker](https://github.com/nhunzaker) in [#7359](https://github.com/facebook/react/pull/7359)) +* Added `react-dom/test-utils`, which exports the React Test Utils. ([@bvaughn](https://github.com/bvaughn)) + +### React Test Renderer + +* Fixed bug where `componentWillUnmount` was not called for children. ([@gre](https://github.com/gre) in [#8512](https://github.com/facebook/react/pull/8512)) +* Added `react-test-renderer/shallow`, which exports the shallow renderer. ([@bvaughn](https://github.com/bvaughn)) + +### React Addons + +* Last release for addons; they will no longer be actively maintained. +* Removed `peerDependencies` so that addons continue to work indefinitely. ([@acdlite](https://github.com/acdlite) and [@bvaughn](https://github.com/bvaughn) in [8a06cd7](https://github.com/facebook/react/commit/8a06cd7a786822fce229197cac8125a551e8abfa) and [67a8db3](https://github.com/facebook/react/commit/67a8db3650d724a51e70be130e9008806402678a)) +* Updated to remove references to `React.createClass` and `React.PropTypes` ([@acdlite](https://github.com/acdlite) in [12a96b9](https://github.com/facebook/react/commit/12a96b94823d6b6de6b1ac13bd576864abd50175)) +* `react-addons-test-utils` is deprecated. Use `react-dom/test-utils` and `react-test-renderer/shallow` instead. ([@bvaughn](https://github.com/bvaughn)) + ## 15.4.2 (January 6, 2017) ### React diff --git a/README.md b/README.md index df717360fee9a..2ae261c6994a9 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ React is flexible and can be used in a variety of projects. You can create new a The recommended way to install React depends on your project. Here you can find short guides for the most common scenarios: * [Trying Out React](https://facebook.github.io/react/docs/installation.html#trying-out-react) -* [Creating a Single Page Application](https://facebook.github.io/react/docs/installation.html#creating-a-single-page-application) +* [Creating a New Application](https://facebook.github.io/react/docs/installation.html#creating-a-new-application) * [Adding React to an Existing Application](https://facebook.github.io/react/docs/installation.html#adding-react-to-an-existing-application) ## Contributing @@ -53,9 +53,9 @@ Facebook has adopted a Code of Conduct that we expect project participants to ad Read our [contributing guide](https://facebook.github.io/react/contributing/how-to-contribute.html) to learn about our development process, how to propose bugfixes and improvements, and how to build and test your changes to React. -### Good First Bug +### Beginner Friendly Bugs -To help you get your feet wet and get you familiar with our contribution process, we have a list of [good first bugs](https://github.com/facebook/react/labels/good%20first%20bug) that contain bugs which are fairly easy to fix. This is a great place to get started. +To help you get your feet wet and get you familiar with our contribution process, we have a list of [beginner friendly bugs](https://github.com/facebook/react/labels/Difficulty%3A%20beginner) that contain bugs which are fairly easy to fix. This is a great place to get started. ### License diff --git a/docs/_data/authors.yml b/docs/_data/authors.yml index eff00b7e2eb59..d52ee1d96d528 100644 --- a/docs/_data/authors.yml +++ b/docs/_data/authors.yml @@ -1,6 +1,9 @@ # Map of short name to more information. `name` will be used but if you don't # want to use your real name, just use whatever. If url is included, your name # will be a link to the provided url. +acdlite: + name: Andrew Clark + url: https://twitter.com/acdlite benigeri: name: Paul Benigeri url: https://github.com/benigeri diff --git a/docs/_posts/2017-04-07-react-v15.5.0.md b/docs/_posts/2017-04-07-react-v15.5.0.md new file mode 100644 index 0000000000000..7c38475c1a483 --- /dev/null +++ b/docs/_posts/2017-04-07-react-v15.5.0.md @@ -0,0 +1,227 @@ +--- +title: "React v15.5.0" +author: acdlite +--- + +It's been exactly one year since the last breaking change to React. Our next major release, React 16, will include some exciting improvements, including a [complete rewrite](https://www.youtube.com/watch?v=ZCuYPiUIONs) of React's internals. [We take stability seriously](/react/contributing/design-principles.html#stability), and are committed to bringing those improvements to all of our users with minimal effort. + +To that end, today we're releasing React 15.5.0. + +### New Deprecation Warnings + +The biggest change is that we've extracted `React.PropTypes` and `React.createClass` into their own packages. Both are still accessible via the main `React` object, but using either will log a one-time deprecation warning to the console when in development mode. This will enable future code size optimizations. + +These warnings will not affect the behavior of your application. However, we realize they may cause some frustration, particularly if you use a testing framework that treats `console.error` as a failure. + +**Adding new warnings is not something we do lightly.** Warnings in React are not mere suggestions — they are integral to our strategy of keeping as many people as possible on the latest version of React. We never add warnings without providing an incremental path forward. + +So while the warnings may cause frustration in the short-term, we believe **prodding developers to migrate their codebases now prevents greater frustration in the future**. Proactively fixing warnings ensures you are prepared for the next major release. If your app produces zero warnings in 15.5, it should continue to work in 16 without any changes. + +For each of these new deprecations, we've provided a codemod to automatically migrate your code. They are available as part of the [react-codemod](https://github.com/reactjs/react-codemod) project. + +### Migrating from React.PropTypes + +Prop types are a feature for runtime validation of props during development. We've extracted the built-in prop types to a separate package to reflect the fact that not everybody uses them. + +In 15.5, instead of accessing `PropTypes` from the main `React` object, install the `prop-types` package and import them from there: + +```js{11,16,25} +// Before (15.4 and below) +import React from 'react'; + +class Component extends React.Component { + render() { + return
{this.props.text}
; + } +} + +Component.propTypes = { + text: React.PropTypes.string.isRequired, +} + +// After (15.5) +import React from 'react'; +import PropTypes from 'prop-types'; + +class Component extends React.Component { + render() { + return
{this.props.text}
; + } +} + +Component.propTypes = { + text: PropTypes.string.isRequired, +}; +``` + +The [codemod](https://github.com/reactjs/react-codemod#react-proptypes-to-prop-types) for this change performs this conversion automatically. Basic usage: + +```bash +jscodeshift -t react-codemod/transforms/React-PropTypes-to-prop-types.js +``` + + +The `propTypes`, `contextTypes`, and `childContextTypes` APIs will work exactly as before. The only change is that the built-in validators now live in a separate package. + +You may also consider using [Flow](https://flow.org/) to statically type check your JavaScript code, including [React components](https://flow.org/en/docs/frameworks/react/#setup-flow-with-react-a-classtoc-idtoc-setup-flow-with-react-hreftoc-setup-flow-with-reacta). + +### Migrating from React.createClass + +When React was initially released, there was no idiomatic way to create classes in JavaScript, so we provided our own: `React.createClass`. + +Later, classes were added to the language as part of ES2015, so we added the ability to create React components using JavaScript classes. **Along with functional components, JavaScript classes are now the [preferred way to create components in React](/react/docs/components-and-props.html#functional-and-class-components).** + +For your existing `createClass` components, we recommend that you migrate them to JavaScript classes. However, if you have components that rely on mixins, converting to classes may not be immediately feasible. If so, `create-react-class` is available on npm as a drop-in replacement: + +```js{4,13,15} +// Before (15.4 and below) +var React = require('react'); + +var Component = React.createClass({ + mixins: [MixinA], + render() { + return ; + } +}); + +// After (15.5) +var React = require('react'); +var createReactClass = require('create-react-class'); + +var Component = createReactClass({ + mixins: [MixinA], + render() { + return ; + } +}); +``` + +Your components will continue to work the same as they did before. + +The [codemod](https://github.com/reactjs/react-codemod#explanation-of-the-new-es2015-class-transform-with-property-initializers) for this change attempts to convert a `createClass` component to a JavaScript class, with a fallback to `create-react-class` if necessary. It has converted thousands of components internally at Facebook. + +Basic usage: + +```bash +jscodeshift -t react-codemod/transforms/class.js path/to/components +``` + +### Discontinuing support for React Addons + +We're discontinuing active maintenance of React Addons packages. In truth, most of these packages haven't been actively maintained in a long time. They will continue to work indefinitely, but we recommend migrating away as soon as you can to prevent future breakages. + +- **react-addons-create-fragment** – React 16 will have first-class support for fragments, at which point this package won't be necessary. We recommend using arrays of keyed elements instead. +- **react-addons-css-transition-group** - Use [react-transition-group/CSSTransitionGroup](https://github.com/reactjs/react-transition-group) instead. Version 1.1.1 provides a drop-in replacement. +- **react-addons-linked-state-mixin** - Explicitly set the `value` and `onChange` handler instead. +- **react-addons-pure-render-mixin** - Use [`React.PureComponent`](/react/docs/react-api.html#react.purecomponent) instead. +- **react-addons-shallow-compare** - Use [`React.PureComponent`](/react/docs/react-api.html#react.purecomponent) instead. +- **react-addons-transition-group** - Use [react-transition-group/TransitionGroup](https://github.com/reactjs/react-transition-group) instead. Version 1.1.1 provides a drop-in replacement. +- **react-addons-update** - Use [immutability-helper](https://github.com/kolodny/immutability-helper) instead, a drop-in replacement. +- **react-linked-input** - Explicitly set the `value` and `onChange` handler instead. + +We're also discontinuing support for the `react-with-addons` UMD build. It will be removed in React 16. + +### React Test Utils + +Currently, the React Test Utils live inside `react-addons-test-utils`. As of 15.5, we're deprecating that package and moving them to `react-dom/test-utils` instead: + +```js +// Before (15.4 and below) +import TestUtils from 'react-addons-test-utils'; + +// After (15.5) +import TestUtils from 'react-dom/test-utils'; +``` + +This reflects the fact that what we call the Test Utils are really a set of APIs that wrap the DOM renderer. + +The exception is shallow rendering, which is not DOM-specific. The shallow renderer has been moved to `react-test-renderer/shallow`. + +```js{2,5} +// Before (15.4 and below) +import { createRenderer } from 'react-addons-test-utils'; + +// After (15.5) +import { createRenderer } from 'react-test-renderer/shallow'; +``` + +--- + +## Acknowledgements + +A special thank you to these folks for transferring ownership of npm package names: + +- [Jason Miller](https://github.com/developit) +- [Aaron Ackerman](https://github.com/aackerman) +- [Vinicius Marson](https://github.com/viniciusmarson) + +--- + +## Installation + +We recommend using [Yarn](https://yarnpkg.com/) or [npm](https://www.npmjs.com/) for managing front-end dependencies. If you're new to package managers, the [Yarn documentation](https://yarnpkg.com/en/docs/getting-started) is a good place to get started. + +To install React with Yarn, run: + +```bash +yarn add react@^15.5.0 react-dom@^15.5.0 +``` + +To install React with npm, run: + +```bash +npm install --save react@^15.5.0 react-dom@^15.5.0 +``` + +We recommend using a bundler like [webpack](https://webpack.js.org/) or [Browserify](http://browserify.org/) so you can write modular code and bundle it together into small packages to optimize load time. + +Remember that by default, React runs extra checks and provides helpful warnings in development mode. When deploying your app, make sure to [compile it in production mode](/react/docs/installation.html#development-and-production-versions). + +In case you don't use a bundler, we also provide pre-built bundles in the npm packages which you can [include as script tags](/react/docs/installation.html#using-a-cdn) on your page: + +* **React** + Dev build with warnings: [react/dist/react.js](https://unpkg.com/react@15.5.0/dist/react.js) + Minified build for production: [react/dist/react.min.js](https://unpkg.com/react@15.5.0/dist/react.min.js) +* **React with Add-Ons** + Dev build with warnings: [react/dist/react-with-addons.js](https://unpkg.com/react@15.5.0/dist/react-with-addons.js) + Minified build for production: [react/dist/react-with-addons.min.js](https://unpkg.com/react@15.5.0/dist/react-with-addons.min.js) +* **React DOM** (include React in the page before React DOM) + Dev build with warnings: [react-dom/dist/react-dom.js](https://unpkg.com/react-dom@15.5.0/dist/react-dom.js) + Minified build for production: [react-dom/dist/react-dom.min.js](https://unpkg.com/react-dom@15.5.0/dist/react-dom.min.js) +* **React DOM Server** (include React in the page before React DOM Server) + Dev build with warnings: [react-dom/dist/react-dom-server.js](https://unpkg.com/react-dom@15.5.0/dist/react-dom-server.js) + Minified build for production: [react-dom/dist/react-dom-server.min.js](https://unpkg.com/react-dom@15.5.0/dist/react-dom-server.min.js) + +We've also published version `15.5.0` of the `react`, `react-dom`, and addons packages on npm and the `react` package on bower. + +--- + +## Changelog + +## 15.5.0 (April 7, 2017) + +### React + +* Added a deprecation warning for `React.createClass`. Points users to create-react-class instead. ([@acdlite](https://github.com/acdlite) in [d9a4fa4](https://github.com/facebook/react/commit/d9a4fa4f51c6da895e1655f32255cf72c0fe620e)) +* Added a deprecation warning for `React.PropTypes`. Points users to prop-types instead. ([@acdlite](https://github.com/acdlite) in [043845c](https://github.com/facebook/react/commit/043845ce75ea0812286bbbd9d34994bb7e01eb28)) +* Fixed an issue when using `ReactDOM` together with `ReactDOMServer`. ([@wacii](https://github.com/wacii) in [#9005](https://github.com/facebook/react/pull/9005)) +* Fixed issue with Closure Compiler. ([@anmonteiro](https://github.com/anmonteiro) in [#8895](https://github.com/facebook/react/pull/8895)) +* Another fix for Closure Compiler. ([@Shastel](https://github.com/Shastel) in [#8882](https://github.com/facebook/react/pull/8882)) +* Added component stack info to invalid element type warning. ([@n3tr](https://github.com/n3tr) in [#8495](https://github.com/facebook/react/pull/8495)) + +### React DOM + +* Fixed Chrome bug when backspacing in number inputs. ([@nhunzaker](https://github.com/nhunzaker) in [#7359](https://github.com/facebook/react/pull/7359)) +* Added `react-dom/test-utils`, which exports the React Test Utils. ([@bvaughn](https://github.com/bvaughn)) + +### React Test Renderer + +* Fixed bug where `componentWillUnmount` was not called for children. ([@gre](https://github.com/gre) in [#8512](https://github.com/facebook/react/pull/8512)) +* Added `react-test-renderer/shallow`, which exports the shallow renderer. ([@bvaughn](https://github.com/bvaughn)) + +### React Addons + +* Last release for addons; they will no longer be actively maintained. +* Removed `peerDependencies` so that addons continue to work indefinitely. ([@acdlite](https://github.com/acdlite) and [@bvaughn](https://github.com/bvaughn) in [8a06cd7](https://github.com/facebook/react/commit/8a06cd7a786822fce229197cac8125a551e8abfa) and [67a8db3](https://github.com/facebook/react/commit/67a8db3650d724a51e70be130e9008806402678a)) +* Updated to remove references to `React.createClass` and `React.PropTypes` ([@acdlite](https://github.com/acdlite) in [12a96b9](https://github.com/facebook/react/commit/12a96b94823d6b6de6b1ac13bd576864abd50175)) +* `react-addons-test-utils` is deprecated. Use `react-dom/test-utils` and `react-test-renderer/shallow` instead. ([@bvaughn](https://github.com/bvaughn)) diff --git a/docs/community/conferences.md b/docs/community/conferences.md index f16f7650a598a..fdb7fdb25f881 100644 --- a/docs/community/conferences.md +++ b/docs/community/conferences.md @@ -14,16 +14,16 @@ March 28th at the [QEII Centre, London](http://qeiicentre.london/) [Website](http://react.london/) -### ReactEurope 2017 -May 18th & 19th in Paris, France - -[Website](http://www.react-europe.org/) - [Schedule](http://www.react-europe.org/#schedule) - ### React Amsterdam 2017 April 21st in Amsterdam, The Netherlands [Website](https://react.amsterdam) - [Twitter](https://twitter.com/reactamsterdam) +### ReactEurope 2017 +May 18th & 19th in Paris, France + +[Website](http://www.react-europe.org/) - [Schedule](http://www.react-europe.org/#schedule) + ### Chain React 2017 July 10-11 in Portland, Oregon USA @@ -34,6 +34,11 @@ August 24-25 in Salt Lake City, Utah USA [Website](http://www.reactrally.com) - [Twitter](https://twitter.com/reactrally) +### React Native EU 2017 +September 6-7 in Wroclaw, Poland + +[Website](http://react-native.eu/) + ### ReactJS Day 2017 October 6th in Verona, Italy @@ -44,11 +49,6 @@ October 13 in Stockholm, Sweden [Website](https://statejs.com/) -### React Native EU 2017 -September 6-7 in Wroclaw, Poland - -[Website](http://react-native.eu/) - ## Past Conferences diff --git a/docs/contributing/codebase-overview.md b/docs/contributing/codebase-overview.md index 045f2c9030231..b9be535b416bc 100644 --- a/docs/contributing/codebase-overview.md +++ b/docs/contributing/codebase-overview.md @@ -66,7 +66,6 @@ After cloning the [React repository](https://github.com/facebook/react), you wil * [`src`](https://github.com/facebook/react/tree/master/src) is the source code of React. **If your change is related to the code, `src` is where you'll spend most of your time.** * [`docs`](https://github.com/facebook/react/tree/master/docs) is the React documentation website. When you change APIs, make sure to update the relevant Markdown files. -* [`examples`](https://github.com/facebook/react/tree/master/examples) contains a few small React demos with different build setups. * [`packages`](https://github.com/facebook/react/tree/master/packages) contains metadata (such as `package.json`) for all packages in the React repository. Nevertheless, their source code is still located inside [`src`](https://github.com/facebook/react/tree/master/src). * `build` is the build output of React. It is not in the repository but it will appear in your React clone after you [build it](/react/contributing/how-to-contribute.html#development-workflow) for the first time. @@ -302,10 +301,8 @@ While the code is separated in the source tree, the exact package boundaries are The "core" of React includes all the [top-level `React` APIs](/react/docs/top-level-api.html#react), for example: * `React.createElement()` -* `React.createClass()` * `React.Component` * `React.Children` -* `React.PropTypes` **React core only includes the APIs necessary to define components.** It does not include the [reconciliation](/react/docs/reconciliation.html) algorithm or any platform-specific code. It is used both by React DOM and React Native components. diff --git a/docs/contributing/how-to-contribute.md b/docs/contributing/how-to-contribute.md index 5b2c6c5031171..b45eb1798b949 100644 --- a/docs/contributing/how-to-contribute.md +++ b/docs/contributing/how-to-contribute.md @@ -64,7 +64,7 @@ Working on your first Pull Request? You can learn how from this free video serie **[How to Contribute to an Open Source Project on GitHub](https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github)** -To help you get your feet wet and get you familiar with our contribution process, we have a list of **[good first bugs](https://github.com/facebook/react/labels/good%20first%20bug)** that contain bugs which are fairly easy to fix. This is a great place to get started. +To help you get your feet wet and get you familiar with our contribution process, we have a list of **[beginner friendly issues](https://github.com/facebook/react/issues?q=is%3Aopen+is%3Aissue+label%3A%22Difficulty%3A+beginner%22)** that contain bugs which are fairly easy to fix. This is a great place to get started. If you decide to fix an issue, please be sure to check the comment thread in case somebody is already working on a fix. If nobody is working on it at the moment, please leave a comment stating that you intend to work on it so other people don't accidentally duplicate your effort. @@ -101,7 +101,7 @@ In order to accept your pull request, we need you to submit a CLA. You only need ### Development Workflow -After cloning React, run `npm install` to fetch its dependencies. +After cloning React, run `npm install` to fetch its dependencies. Then, you can run several commands: * `npm run lint` checks the code style. diff --git a/docs/contributing/implementation-notes.md b/docs/contributing/implementation-notes.md index 2252a006cac71..2309dd721cc1b 100644 --- a/docs/contributing/implementation-notes.md +++ b/docs/contributing/implementation-notes.md @@ -11,6 +11,8 @@ This section is a collection of implementation notes for the [stack reconciler]( It is very technical and assumes a strong understanding of React public API as well as how it's divided into core, renderers, and the reconciler. If you're not very familiar with the React codebase, read [the codebase overview](/react/contributing/codebase-overview.html) first. +It also assumes an understanding of the [differences between React components, their instances, and elements](/react/blog/2015/12/18/react-components-elements-and-instances.html). + The stack reconciler is powering all the React production code today. It is located in [`src/renderers/shared/stack/reconciler`](https://github.com/facebook/react/tree/master/src/renderers/shared/stack) and is used by both React DOM and React Native. ### Video: Building React from Scratch diff --git a/docs/docs/addons.md b/docs/docs/addons.md index 982f5e481fc59..d0f6a770b8ec4 100644 --- a/docs/docs/addons.md +++ b/docs/docs/addons.md @@ -34,8 +34,8 @@ The add-ons below are considered legacy and their use is discouraged. You can install the add-ons individually from npm (e.g. `npm install react-addons-create-fragment`) and import them: ```javascript -import Perf from 'react-addons-perf'; // ES6 -var Perf = require('react-addons-perf'); // ES5 with npm +import createFragment from 'react-addons-create-fragment'; // ES6 +var createFragment = require('react-addons-create-fragment'); // ES5 with npm ``` When using a CDN, you can use `react-with-addons.js` instead of `react.js`: diff --git a/docs/docs/components-and-props.md b/docs/docs/components-and-props.md index d1bf5ca9d3664..150138fb91b60 100644 --- a/docs/docs/components-and-props.md +++ b/docs/docs/components-and-props.md @@ -12,6 +12,8 @@ redirect_from: - "docs/transferring-props-zh-CN.html" - "tips/props-in-getInitialState-as-anti-pattern.html" - "tips/communicate-between-components.html" +prev: rendering-elements.html +next: state-and-lifecycle.html --- Components let you split the UI into independent, reusable pieces, and think about each piece in isolation. diff --git a/docs/docs/composition-vs-inheritance.md b/docs/docs/composition-vs-inheritance.md index 2817811a669c4..0ac5cd45b21cc 100644 --- a/docs/docs/composition-vs-inheritance.md +++ b/docs/docs/composition-vs-inheritance.md @@ -3,6 +3,8 @@ id: composition-vs-inheritance title: Composition vs Inheritance permalink: docs/composition-vs-inheritance.html redirect_from: "docs/multiple-components.html" +prev: lifting-state-up.html +next: thinking-in-react.html --- React has a powerful composition model, and we recommend using composition instead of inheritance to reuse code between components. diff --git a/docs/docs/conditional-rendering.md b/docs/docs/conditional-rendering.md index 7cfc60a8f9531..ea1be02a4c850 100644 --- a/docs/docs/conditional-rendering.md +++ b/docs/docs/conditional-rendering.md @@ -2,6 +2,8 @@ id: conditional-rendering title: Conditional Rendering permalink: docs/conditional-rendering.html +prev: handling-events.html +next: lists-and-keys.html redirect_from: "tips/false-in-jsx.html" --- diff --git a/docs/docs/context.md b/docs/docs/context.md index e3efaf5d5b308..718e941a06418 100644 --- a/docs/docs/context.md +++ b/docs/docs/context.md @@ -5,7 +5,7 @@ permalink: docs/context.html --- >Note: -> As of React v15.5 the `React.PropTypes` helper is deprecated, and we recommend using the [`prop-types` library](https://github.com/aackerman/PropTypes) to define `contextTypes`. +> As of React v15.5 the `React.PropTypes` helper is deprecated, and we recommend using the [`prop-types` library](https://www.npmjs.com/package/prop-types) to define `contextTypes`. With React, it's easy to track the flow of data through your React components. When you look at a component, you can see which props are being passed, which makes your apps easy to reason about. diff --git a/docs/docs/installation.md b/docs/docs/installation.md index d4ee06ae54422..3072d7768e7eb 100644 --- a/docs/docs/installation.md +++ b/docs/docs/installation.md @@ -11,27 +11,86 @@ redirect_from: - "docs/environments.html" next: hello-world.html --- + React is flexible and can be used in a variety of projects. You can create new apps with it, but you can also gradually introduce it into an existing codebase without doing a rewrite. +
+ + + Which of these options best describes what you want to do? +
+
+ Try React + Create a New App + Add React to an Existing App +
+ + + ## Trying Out React If you're just interested in playing around with React, you can use CodePen. Try starting from [this Hello World example code](http://codepen.io/gaearon/pen/rrpgNB?editors=0010). You don't need to install anything; you can just modify the code and see if it works. If you prefer to use your own text editor, you can also download this HTML file, edit it, and open it from the local filesystem in your browser. It does a slow runtime code transformation, so don't use it in production. -## Creating a Single Page Application +If you want to use it for a full application, there are two popular ways to get started with React: using Create React App, or adding it to an existing application. + + + +## Creating a New Application [Create React App](http://github.com/facebookincubator/create-react-app) is the best way to start building a new React single page application. It sets up your development environment so that you can use the latest JavaScript features, provides a nice developer experience, and optimizes your app for production. ```bash npm install -g create-react-app -create-react-app hello-world -cd hello-world +create-react-app my-app + +cd my-app npm start ``` -Create React App doesn't handle backend logic or databases; it just creates a frontend build pipeline, so you can use it with any backend you want. It uses [webpack](https://webpack.js.org/), [Babel](http://babeljs.io/) and [ESLint](http://eslint.org/) under the hood, but configures them for you. +Create React App doesn't handle backend logic or databases; it just creates a frontend build pipeline, so you can use it with any backend you want. It uses build tools like Babel and webpack under the hood, but works with zero configuration. + + ## Adding React to an Existing Application @@ -134,3 +193,69 @@ The versions above are only meant for development, and are not suitable for prod To load a specific version of `react` and `react-dom`, replace `15` with the version number. If you use Bower, React is available via the `react` package. + + \ No newline at end of file diff --git a/docs/docs/jsx-in-depth.md b/docs/docs/jsx-in-depth.md index 41c3813dd8459..d75fd72fc4f4d 100644 --- a/docs/docs/jsx-in-depth.md +++ b/docs/docs/jsx-in-depth.md @@ -152,7 +152,7 @@ function Story(props) { To fix this, we will assign the type to a capitalized variable first: -```js{9-11} +```js{10-12} import React from 'react'; import { PhotoStory, VideoStory } from './stories'; @@ -172,7 +172,7 @@ function Story(props) { There are several different ways to specify props in JSX. -### JavaScript Expressions +### JavaScript Expressions as Props You can pass any JavaScript expression as a prop, by surrounding it with `{}`. For example, in this JSX: @@ -308,7 +308,7 @@ You can mix together different types of children, so you can use string literals A React component can't return multiple React elements, but a single JSX expression can have multiple children, so if you want a component to render multiple things you can wrap it in a `div` like this. -### JavaScript Expressions +### JavaScript Expressions as Children You can pass any JavaScript expression as children, by enclosing it within `{}`. For example, these expressions are equivalent: diff --git a/docs/docs/lifting-state-up.md b/docs/docs/lifting-state-up.md index 60931231cbc23..17b4406720518 100644 --- a/docs/docs/lifting-state-up.md +++ b/docs/docs/lifting-state-up.md @@ -302,7 +302,7 @@ Let's recap what happens when you edit an input: * React calls the function specified as `onChange` on the DOM ``. In our case, this is the `handleChange` method in `TemperatureInput` component. * The `handleChange` method in the `TemperatureInput` component calls `this.props.onTemperatureChange()` with the new desired value. Its props, including `onTemperatureChange`, were provided by its parent component, the `Calculator`. -* When it previously rendered, the `Calculator` has specified that `onTemperatureChange` of the Celsius `TemperatureInput` is the `Calculator`'s `handleCelsiusChange` method, and `onTemperatureChange` of the Fahrenheit `TemperatureInput` is the `Calculator`'s `handleFahrehnheitChange` method. So either of these two `Calculator` methods gets called depending on which input we edited. +* When it previously rendered, the `Calculator` has specified that `onTemperatureChange` of the Celsius `TemperatureInput` is the `Calculator`'s `handleCelsiusChange` method, and `onTemperatureChange` of the Fahrenheit `TemperatureInput` is the `Calculator`'s `handleFahrenheitChange` method. So either of these two `Calculator` methods gets called depending on which input we edited. * Inside these methods, the `Calculator` component asks React to re-render itself by calling `this.setState()` with the new input value and the current scale of the input we just edited. * React calls the `Calculator` component's `render` method to learn what the UI should look like. The values of both inputs are recomputed based on the current temperature and the active scale. The temperature conversion is performed here. * React calls the `render` methods of the individual `TemperatureInput` components with their new props specified by the `Calculator`. It learns what their UI should look like. diff --git a/docs/docs/reference-react-component.md b/docs/docs/reference-react-component.md index d8c2be7302fec..2c1b0770cb871 100644 --- a/docs/docs/reference-react-component.md +++ b/docs/docs/reference-react-component.md @@ -113,7 +113,7 @@ The constructor for a React component is called before it is mounted. When imple The constructor is the right place to initialize state. If you don't initialize state and you don't bind methods, you don't need to implement a constructor for your React component. -It's okay to initialize state based on props if you know what you're doing. Here's an example of a valid `React.Component` subclass constructor: +It's okay to initialize state based on props. This effectively "forks" the props and sets the state with the initial props. Here's an example of a valid `React.Component` subclass constructor: ```js constructor(props) { @@ -124,7 +124,7 @@ constructor(props) { } ``` -Beware of this pattern, as it effectively "forks" the props and can lead to bugs. Instead of syncing props to state, you often want to [lift the state up](/react/docs/lifting-state-up.html). +Beware of this pattern, as state won't be up-to-date with any props update. Instead of syncing props to state, you often want to [lift the state up](/react/docs/lifting-state-up.html). If you "fork" props by using them for state, you might also want to implement [`componentWillReceiveProps(nextProps)`](#componentwillreceiveprops) to keep the state up-to-date with them. But lifting state up is often easier and less bug-prone. @@ -136,7 +136,7 @@ If you "fork" props by using them for state, you might also want to implement [` componentWillMount() ``` -`componentWillMount()` is invoked immediately before mounting occurs. It is called before `render()`, therefore setting state in this method will not trigger a re-rendering. Avoid introducing any side-effects or subscriptions in this method. +`componentWillMount()` is invoked immediately before mounting occurs. It is called before `render()`, therefore setting state synchronously in this method will not trigger a re-rendering. Avoid introducing any side-effects or subscriptions in this method. This is the only lifecycle hook called on server rendering. Generally, we recommend using the `constructor()` instead. @@ -229,34 +229,67 @@ componentWillUnmount() ### `setState()` ```javascript -setState(nextState, callback) +setState(updater, [callback]) ``` -Performs a shallow merge of nextState into current state. This is the primary method you use to trigger UI updates from event handlers and server request callbacks. +`setState()` enqueues changes to the component state and tells React that this component and its children need to be re-rendered with the updated state. This is the primary method you use to update the user interface in response to event handlers and server responses. -The first argument can be an object (containing zero or more keys to update) or a function (of state and props) that returns an object containing keys to update. +Think of `setState()` as a *request* rather than an immediate command to update the component. For better perceived performance, React may delay it, and then update several components in a single pass. React does not guarantee that the state changes are applied immediately. -Here is the simple object usage: +`setState()` does not always immediately update the component. It may batch or defer the update until later. This makes reading `this.state` right after calling `setState()` a potential pitfall. Instead, use `componentDidUpdate` or a `setState` callback (`setState(updater, callback)`), either of which are guaranteed to fire after the update has been applied. If you need to set the state based on the previous state, read about the `updater` argument below. + +`setState()` will always lead to a re-render unless `shouldComponentUpdate()` returns `false`. If mutable objects are being used and conditional rendering logic cannot be implemented in `shouldComponentUpdate()`, calling `setState()` only when the new state differs from the previous state will avoid unnecessary re-renders. + +The first argument is an `updater` function with the signature: ```javascript -this.setState({mykey: 'my new value'}); +(prevState, props) => nextState ``` -It's also possible to pass a function with the signature `function(state, props) => newState`. This enqueues an atomic update that consults the previous value of state and props before setting any values. For instance, suppose we wanted to increment a value in state by `props.step`: +`prevState` is a reference to the previous state. It should not be directly mutated. Instead, changes should be represented by building a new state object based on the input from `prevState` and `props`. For instance, suppose we wanted to increment a value in state by `props.step`: ```javascript this.setState((prevState, props) => { - return {myInteger: prevState.myInteger + props.step}; + return {counter: prevState.counter + props.step}; }); ``` -The second parameter is an optional callback function that will be executed once `setState` is completed and the component is re-rendered. Generally we recommend using `componentDidUpdate()` for such logic instead. +Both `prevState` and `props` received by the updater function are guaranteed to be up-to-date. -`setState()` does not immediately mutate `this.state` but creates a pending state transition. Accessing `this.state` after calling this method can potentially return the existing value. +The second parameter to `setState()` is an optional callback function that will be executed once `setState` is completed and the component is re-rendered. Generally we recommend using `componentDidUpdate()` for such logic instead. -There is no guarantee of synchronous operation of calls to `setState` and calls may be batched for performance gains. +You may optionally pass an object as the first argument to `setState()` instead of a function: -`setState()` will always lead to a re-render unless `shouldComponentUpdate()` returns `false`. If mutable objects are being used and conditional rendering logic cannot be implemented in `shouldComponentUpdate()`, calling `setState()` only when the new state differs from the previous state will avoid unnecessary re-renders. +```javascript +setState(stateChange, [callback]) +``` + +This performs a shallow merge of `stateChange` into the new state, e.g., to adjust a shopping cart item quantity: + +```javascript +this.setState({quantity: 2}) +``` + +This form of `setState()` is also asynchronous, and multiple calls during the same cycle may be batched together. For example, if you attempt to increment an item quantity more than once in the same cycle, that will result in the equivalent of: + +```javaScript +Object.assign( + previousState, + {quantity: state.quantity + 1}, + {quantity: state.quantity + 1}, + ... +) +``` + +Subsequent calls will override values from previous calls in the same cycle, so the quantity will only be incremented once. If the next state depends on the previous state, we recommend using the updater function form, instead: + +```js +this.setState((prevState) => { + return {counter: prevState.quantity + 1}; +}); +``` + +For more detail, see the [State and Lifecycle guide](/react/docs/state-and-lifecycle.html). * * * diff --git a/docs/docs/typechecking-with-proptypes.md b/docs/docs/typechecking-with-proptypes.md index 63c3241c2e1ce..85c128d3a909f 100644 --- a/docs/docs/typechecking-with-proptypes.md +++ b/docs/docs/typechecking-with-proptypes.md @@ -7,11 +7,13 @@ redirect_from: --- > Note: -> `React.PropTypes` is deprecated as of React v15.5. Please use [the `prop-types` library instead](https://github.com/aackerman/PropTypes). +> `React.PropTypes` is deprecated as of React v15.5. Please use [the `prop-types` library instead](https://www.npmjs.com/package/prop-types). As your app grows, you can catch a lot of bugs with typechecking. For some applications, you can use JavaScript extensions like [Flow](https://flowtype.org/) or [TypeScript](https://www.typescriptlang.org/) to typecheck your whole application. But even if you don't use those, React has some built-in typechecking abilities. To run typechecking on the props for a component, you can assign the special `propTypes` property: ```javascript +import PropTypes from 'prop-types'; + class Greeting extends React.Component { render() { return ( @@ -21,68 +23,70 @@ class Greeting extends React.Component { } Greeting.propTypes = { - name: React.PropTypes.string + name: PropTypes.string }; ``` -`React.PropTypes` exports a range of validators that can be used to make sure the data you receive is valid. In this example, we're using `React.PropTypes.string`. When an invalid value is provided for a prop, a warning will be shown in the JavaScript console. For performance reasons, `propTypes` is only checked in development mode. +`PropTypes` exports a range of validators that can be used to make sure the data you receive is valid. In this example, we're using `PropTypes.string`. When an invalid value is provided for a prop, a warning will be shown in the JavaScript console. For performance reasons, `propTypes` is only checked in development mode. -### React.PropTypes +### PropTypes Here is an example documenting the different validators provided: ```javascript +import PropTypes from 'prop-types'; + MyComponent.propTypes = { // You can declare that a prop is a specific JS primitive. By default, these // are all optional. - optionalArray: React.PropTypes.array, - optionalBool: React.PropTypes.bool, - optionalFunc: React.PropTypes.func, - optionalNumber: React.PropTypes.number, - optionalObject: React.PropTypes.object, - optionalString: React.PropTypes.string, - optionalSymbol: React.PropTypes.symbol, + optionalArray: PropTypes.array, + optionalBool: PropTypes.bool, + optionalFunc: PropTypes.func, + optionalNumber: PropTypes.number, + optionalObject: PropTypes.object, + optionalString: PropTypes.string, + optionalSymbol: PropTypes.symbol, // Anything that can be rendered: numbers, strings, elements or an array // (or fragment) containing these types. - optionalNode: React.PropTypes.node, + optionalNode: PropTypes.node, // A React element. - optionalElement: React.PropTypes.element, + optionalElement: PropTypes.element, // You can also declare that a prop is an instance of a class. This uses // JS's instanceof operator. - optionalMessage: React.PropTypes.instanceOf(Message), + optionalMessage: PropTypes.instanceOf(Message), // You can ensure that your prop is limited to specific values by treating // it as an enum. - optionalEnum: React.PropTypes.oneOf(['News', 'Photos']), + optionalEnum: PropTypes.oneOf(['News', 'Photos']), // An object that could be one of many types - optionalUnion: React.PropTypes.oneOfType([ - React.PropTypes.string, - React.PropTypes.number, - React.PropTypes.instanceOf(Message) + optionalUnion: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.number, + PropTypes.instanceOf(Message) ]), // An array of a certain type - optionalArrayOf: React.PropTypes.arrayOf(React.PropTypes.number), + optionalArrayOf: PropTypes.arrayOf(PropTypes.number), // An object with property values of a certain type - optionalObjectOf: React.PropTypes.objectOf(React.PropTypes.number), + optionalObjectOf: PropTypes.objectOf(PropTypes.number), // An object taking on a particular shape - optionalObjectWithShape: React.PropTypes.shape({ - color: React.PropTypes.string, - fontSize: React.PropTypes.number + optionalObjectWithShape: PropTypes.shape({ + color: PropTypes.string, + fontSize: PropTypes.number }), // You can chain any of the above with `isRequired` to make sure a warning // is shown if the prop isn't provided. - requiredFunc: React.PropTypes.func.isRequired, + requiredFunc: PropTypes.func.isRequired, // A value of any data type - requiredAny: React.PropTypes.any.isRequired, + requiredAny: PropTypes.any.isRequired, // You can also specify a custom validator. It should return an Error // object if the validation fails. Don't `console.warn` or throw, as this @@ -101,7 +105,7 @@ MyComponent.propTypes = { // will be called for each key in the array or object. The first two // arguments of the validator are the array or object itself, and the // current item's key. - customArrayProp: React.PropTypes.arrayOf(function(propValue, key, componentName, location, propFullName) { + customArrayProp: PropTypes.arrayOf(function(propValue, key, componentName, location, propFullName) { if (!/matchme/.test(propValue[key])) { return new Error( 'Invalid prop `' + propFullName + '` supplied to' + @@ -114,9 +118,11 @@ MyComponent.propTypes = { ### Requiring Single Child -With `React.PropTypes.element` you can specify that only a single child can be passed to a component as children. +With `PropTypes.element` you can specify that only a single child can be passed to a component as children. ```javascript +import PropTypes from 'prop-types'; + class MyComponent extends React.Component { render() { // This must be exactly one element or it will warn. @@ -130,7 +136,7 @@ class MyComponent extends React.Component { } MyComponent.propTypes = { - children: React.PropTypes.element.isRequired + children: PropTypes.element.isRequired }; ``` diff --git a/docs/warnings/dont-call-proptypes.md b/docs/warnings/dont-call-proptypes.md index bc468689af3ba..05da5c04c73ec 100644 --- a/docs/warnings/dont-call-proptypes.md +++ b/docs/warnings/dont-call-proptypes.md @@ -5,7 +5,7 @@ permalink: warnings/dont-call-proptypes.html --- > Note: -> `React.PropTypes` is deprecated as of React v15.5. Please use [the `prop-types` library instead](https://github.com/aackerman/PropTypes). +> `React.PropTypes` is deprecated as of React v15.5. Please use [the `prop-types` library instead](https://github.com/reactjs/prop-types). In a future major release of React, the code that implements PropType validation functions will be stripped in production. Once this happens, any code that calls these functions manually (that isn't stripped in production) will throw an error. diff --git a/eslint-rules/__tests__/no-primitive-constructors-test.js b/eslint-rules/__tests__/no-primitive-constructors-test.js index 5d1a19ef18ce4..d756beabc34a2 100644 --- a/eslint-rules/__tests__/no-primitive-constructors-test.js +++ b/eslint-rules/__tests__/no-primitive-constructors-test.js @@ -34,8 +34,10 @@ ruleTester.run('eslint-rules/no-primitive-constructors', rule, { code: 'String(obj)', errors: [ { - message: 'Do not use the String constructor. To cast a value to a string, ' + - 'concat it with the empty string (unless it\'s a symbol, which has different semantics): \'\' + value', + message: + 'Do not use the String constructor. ' + + 'To cast a value to a string, concat it with the empty string ' + + '(unless it\'s a symbol, which has different semantics): \'\' + value', }, ], }, diff --git a/fixtures/art/VectorWidget.js b/fixtures/art/VectorWidget.js index dda64bee188db..da5cde9e4be3f 100644 --- a/fixtures/art/VectorWidget.js +++ b/fixtures/art/VectorWidget.js @@ -24,13 +24,11 @@ var BASE_VEL = 0.15; /** * An animated SVG component. */ -var VectorWidget = React.createClass({ +class VectorWidget extends React.Component { /** * Initialize state members. */ - getInitialState: function() { - return {degrees: 0, velocity: 0, drag: MOUSE_UP_DRAG}; - }, + state = {degrees: 0, velocity: 0, drag: MOUSE_UP_DRAG}; /** * When the component is mounted into the document - this is similar to a @@ -39,40 +37,40 @@ var VectorWidget = React.createClass({ * method. Binding of `this.onTick` is not needed because all React methods * are automatically bound before being mounted. */ - componentDidMount: function() { + componentDidMount() { this._interval = window.setInterval(this.onTick, 20); - }, + } - componentWillUnmount: function() { + componentWillUnmount() { window.clearInterval(this._interval); - }, + } - onTick: function() { + onTick = () => { var nextDegrees = this.state.degrees + BASE_VEL + this.state.velocity; var nextVelocity = this.state.velocity * this.state.drag; this.setState({degrees: nextDegrees, velocity: nextVelocity}); - }, + }; /** * When mousing down, we increase the friction down the velocity. */ - handleMouseDown: function() { + handleMouseDown = () => { this.setState({drag: MOUSE_DOWN_DRAG}); - }, + }; /** * Cause the rotation to "spring". */ - handleMouseUp: function() { + handleMouseUp = () => { var nextVelocity = Math.min(this.state.velocity + CLICK_ACCEL, MAX_VEL); this.setState({velocity: nextVelocity, drag: MOUSE_UP_DRAG}); - }, + }; /** * This is the "main" method for any component. The React API allows you to * describe the structure of your UI component at *any* point in time. */ - render: function() { + render() { return ( ); - }, + } /** * Better SVG support for React coming soon. */ - renderGraphic: function(rotation) { + renderGraphic = (rotation) => { return ( ); - } -}); + }; +} var BORDER_PATH = "M3.00191459,4 C1.34400294,4 0,5.34785514 0,7.00550479 L0,220.994495 C0,222.65439 1.34239483,224 3.00191459,224 L276.998085,224 C278.655997,224 280,222.652145 280,220.994495 L280,7.00550479 C280,5.34561033 278.657605,4 276.998085,4 L3.00191459,4 Z M3.00191459,4"; var BG_PATH = "M3.00191459,1 C1.34400294,1 0,2.34785514 0,4.00550479 L0,217.994495 C0,219.65439 1.34239483,221 3.00191459,221 L276.998085,221 C278.655997,221 280,219.652145 280,217.994495 L280,4.00550479 C280,2.34561033 278.657605,1 276.998085,1 L3.00191459,1 Z M3.00191459,1"; diff --git a/fixtures/dom/package.json b/fixtures/dom/package.json index 07a70c4dc1d74..214de66fb73a6 100644 --- a/fixtures/dom/package.json +++ b/fixtures/dom/package.json @@ -8,6 +8,7 @@ "dependencies": { "classnames": "^2.2.5", "query-string": "^4.2.3", + "prop-types": "^15.5.6", "react": "^15.4.1", "react-dom": "^15.4.1", "semver": "^5.3.0" diff --git a/fixtures/dom/public/index.html b/fixtures/dom/public/index.html index a131a92882520..ed1922517a1b9 100644 --- a/fixtures/dom/public/index.html +++ b/fixtures/dom/public/index.html @@ -14,6 +14,7 @@ Learn how to configure a non-root public URL by running `npm run build`. --> React App + diff --git a/fixtures/dom/src/components/App.js b/fixtures/dom/src/components/App.js index 92550c638cff9..a22b1d918c4f6 100644 --- a/fixtures/dom/src/components/App.js +++ b/fixtures/dom/src/components/App.js @@ -4,17 +4,15 @@ import Fixtures from './fixtures'; import '../style.css'; -const App = React.createClass({ - render() { - return ( -
-
-
- -
+function App () { + return ( +
+
+
+
- ); - }, -}); +
+ ); +} export default App; diff --git a/fixtures/dom/src/components/Fixture.js b/fixtures/dom/src/components/Fixture.js index 1f6ef9ba09997..6074fa7d44c4e 100644 --- a/fixtures/dom/src/components/Fixture.js +++ b/fixtures/dom/src/components/Fixture.js @@ -1,7 +1,8 @@ +const PropTypes = window.PropTypes; const React = window.React; const propTypes = { - children: React.PropTypes.node.isRequired, + children: PropTypes.node.isRequired, }; class Fixture extends React.Component { diff --git a/fixtures/dom/src/components/FixtureSet.js b/fixtures/dom/src/components/FixtureSet.js index 9c6e8581eba4b..ff98b2884c1bb 100644 --- a/fixtures/dom/src/components/FixtureSet.js +++ b/fixtures/dom/src/components/FixtureSet.js @@ -1,8 +1,9 @@ import React from 'react'; +import PropTypes from 'prop-types'; const propTypes = { - title: React.PropTypes.node.isRequired, - description: React.PropTypes.node.isRequired, + title: PropTypes.node.isRequired, + description: PropTypes.node.isRequired, }; class FixtureSet extends React.Component { diff --git a/fixtures/dom/src/components/Header.js b/fixtures/dom/src/components/Header.js index d9732165c4248..6d1b32599fa8a 100644 --- a/fixtures/dom/src/components/Header.js +++ b/fixtures/dom/src/components/Header.js @@ -2,13 +2,14 @@ import { parse, stringify } from 'query-string'; import getVersionTags from '../tags'; const React = window.React; -const Header = React.createClass({ - getInitialState() { +class Header extends React.Component { + constructor(props, context) { + super(props, context); const query = parse(window.location.search); const version = query.version || 'local'; const versions = [version]; - return { version, versions }; - }, + this.state = { version, versions }; + } componentWillMount() { getVersionTags() .then(tags => { @@ -16,7 +17,7 @@ const Header = React.createClass({ versions = [`local`, ...versions]; this.setState({ versions }); }) - }, + } handleVersionChange(event) { const query = parse(window.location.search); query.version = event.target.value; @@ -24,10 +25,10 @@ const Header = React.createClass({ delete query.version; } window.location.search = stringify(query); - }, + } handleFixtureChange(event) { window.location.pathname = event.target.value; - }, + } render() { return (
@@ -66,7 +67,7 @@ const Header = React.createClass({
); - }, -}); + } +} export default Header; diff --git a/fixtures/dom/src/components/TestCase.js b/fixtures/dom/src/components/TestCase.js index 79c46a975afbc..053eac9467884 100644 --- a/fixtures/dom/src/components/TestCase.js +++ b/fixtures/dom/src/components/TestCase.js @@ -1,14 +1,15 @@ import cn from 'classnames'; import semver from 'semver'; import React from 'react'; +import PropTypes from 'prop-types'; import { parse } from 'query-string'; -import { semverString } from './propTypes' +import { semverString } from './propTypes'; const propTypes = { - children: React.PropTypes.node.isRequired, - title: React.PropTypes.node.isRequired, + children: PropTypes.node.isRequired, + title: PropTypes.node.isRequired, resolvedIn: semverString, - resolvedBy: React.PropTypes.string + resolvedBy: PropTypes.string }; class TestCase extends React.Component { diff --git a/fixtures/dom/src/components/fixtures/buttons/index.js b/fixtures/dom/src/components/fixtures/buttons/index.js index 4991f4e4d8c6b..e7182280d637b 100644 --- a/fixtures/dom/src/components/fixtures/buttons/index.js +++ b/fixtures/dom/src/components/fixtures/buttons/index.js @@ -10,7 +10,7 @@ function onButtonClick() { export default class ButtonTestCases extends React.Component { render() { return ( - + diff --git a/fixtures/dom/src/components/fixtures/index.js b/fixtures/dom/src/components/fixtures/index.js index adc5a3d16208e..27be0fe24f284 100644 --- a/fixtures/dom/src/components/fixtures/index.js +++ b/fixtures/dom/src/components/fixtures/index.js @@ -12,29 +12,27 @@ import ButtonFixtures from './buttons'; * A simple routing component that renders the appropriate * fixture based on the location pathname. */ -const FixturesPage = React.createClass({ - render() { - switch (window.location.pathname) { - case '/text-inputs': - return ; - case '/range-inputs': - return ; - case '/selects': - return ; - case '/textareas': - return ; - case '/input-change-events': - return ; - case '/number-inputs': - return ; - case '/password-inputs': - return ; - case '/buttons': - return - default: - return

Please select a test fixture.

; - } - }, -}); +function FixturesPage() { + switch (window.location.pathname) { + case '/text-inputs': + return ; + case '/range-inputs': + return ; + case '/selects': + return ; + case '/textareas': + return ; + case '/input-change-events': + return ; + case '/number-inputs': + return ; + case '/password-inputs': + return ; + case '/buttons': + return + default: + return

Please select a test fixture.

; + } +} module.exports = FixturesPage; diff --git a/fixtures/dom/src/components/fixtures/number-inputs/NumberTestCase.js b/fixtures/dom/src/components/fixtures/number-inputs/NumberTestCase.js index 2c072d478ebe4..1a3025c49323b 100644 --- a/fixtures/dom/src/components/fixtures/number-inputs/NumberTestCase.js +++ b/fixtures/dom/src/components/fixtures/number-inputs/NumberTestCase.js @@ -2,16 +2,14 @@ const React = window.React; import Fixture from '../../Fixture'; -const NumberTestCase = React.createClass({ - getInitialState() { - return { value: '' }; - }, - onChange(event) { +class NumberTestCase extends React.Component { + state = { value: '' }; + onChange = (event) => { const parsed = parseFloat(event.target.value, 10) const value = isNaN(parsed) ? '' : parsed this.setState({ value }) - }, + } render() { return ( @@ -31,7 +29,7 @@ const NumberTestCase = React.createClass({ ); - }, -}); + } +} export default NumberTestCase; diff --git a/fixtures/dom/src/components/fixtures/number-inputs/index.js b/fixtures/dom/src/components/fixtures/number-inputs/index.js index 2c88c333edeb3..7900bd8594345 100644 --- a/fixtures/dom/src/components/fixtures/number-inputs/index.js +++ b/fixtures/dom/src/components/fixtures/number-inputs/index.js @@ -4,164 +4,162 @@ import FixtureSet from '../../FixtureSet'; import TestCase from '../../TestCase'; import NumberTestCase from './NumberTestCase'; -const NumberInputs = React.createClass({ - render() { - return ( - + - - -
  • Type "3.1"
  • -
  • Press backspace, eliminating the "1"
  • -
    - - - The field should read "3.", preserving the decimal place - - - - -

    - Notes: Chrome and Safari clear trailing - decimals on blur. React makes this concession so that the - value attribute remains in sync with the value property. -

    -
    - - - -
  • Type "0.01"
  • -
    - - - The field should read "0.01" - - - -
    - - - -
  • Type "2e"
  • -
  • Type 4, to read "2e4"
  • -
    - - - The field should read "2e4". The parsed value should read "20000" - - - -
    - - - -
  • Type "3.14"
  • -
  • Press "e", so that the input reads "3.14e"
  • -
    - - - The field should read "3.14e", the parsed value should be empty - - - -
    - - - -
  • Type "3.14"
  • -
  • Move the text cursor to after the decimal place
  • -
  • Press "e" twice, so that the value reads "3.ee14"
  • -
    - - - The field should read "3.ee14" - - - -
    - - - -
  • Type "3.0"
  • -
    - - - The field should read "3.0" - - - -
    - - - -
  • Type "300"
  • -
  • Move the cursor to after the "3"
  • -
  • Type "."
  • -
    - - - The field should read "3.00", not "3" - - -
    - - - -
  • Type "3"
  • -
  • Select the entire value"
  • -
  • Type '-' to replace '3' with '-'
  • -
    - - - The field should read "-", not be blank. - - -
    - - - -
  • Type "-"
  • -
  • Type '3'
  • -
    - - - The field should read "-3". - - -
    -
    - ); - }, -}); + +
  • Type "3.1"
  • +
  • Press backspace, eliminating the "1"
  • +
    + + + The field should read "3.", preserving the decimal place + + + + +

    + Notes: Chrome and Safari clear trailing + decimals on blur. React makes this concession so that the + value attribute remains in sync with the value property. +

    +
    + + + +
  • Type "0.01"
  • +
    + + + The field should read "0.01" + + + +
    + + + +
  • Type "2e"
  • +
  • Type 4, to read "2e4"
  • +
    + + + The field should read "2e4". The parsed value should read "20000" + + + +
    + + + +
  • Type "3.14"
  • +
  • Press "e", so that the input reads "3.14e"
  • +
    + + + The field should read "3.14e", the parsed value should be empty + + + +
    + + + +
  • Type "3.14"
  • +
  • Move the text cursor to after the decimal place
  • +
  • Press "e" twice, so that the value reads "3.ee14"
  • +
    + + + The field should read "3.ee14" + + + +
    + + + +
  • Type "3.0"
  • +
    + + + The field should read "3.0" + + + +
    + + + +
  • Type "300"
  • +
  • Move the cursor to after the "3"
  • +
  • Type "."
  • +
    + + + The field should read "3.00", not "3" + + +
    + + + +
  • Type "3"
  • +
  • Select the entire value"
  • +
  • Type '-' to replace '3' with '-'
  • +
    + + + The field should read "-", not be blank. + + +
    + + + +
  • Type "-"
  • +
  • Type '3'
  • +
    + + + The field should read "-3". + + +
    +
    + ); +} export default NumberInputs; diff --git a/fixtures/dom/src/components/fixtures/password-inputs/PasswordTestCase.js b/fixtures/dom/src/components/fixtures/password-inputs/PasswordTestCase.js index 76f511776369d..085ff760b646b 100644 --- a/fixtures/dom/src/components/fixtures/password-inputs/PasswordTestCase.js +++ b/fixtures/dom/src/components/fixtures/password-inputs/PasswordTestCase.js @@ -2,13 +2,11 @@ const React = window.React; import Fixture from '../../Fixture'; -const PasswordTestCase = React.createClass({ - getInitialState() { - return { value: '' }; - }, - onChange(event) { +class PasswordTestCase extends React.Component { + state = { value: '' }; + onChange = (event) => { this.setState({ value: event.target.value }) - }, + } render() { return ( @@ -28,7 +26,7 @@ const PasswordTestCase = React.createClass({ ); - }, -}); + } +} export default PasswordTestCase; diff --git a/fixtures/dom/src/components/fixtures/password-inputs/index.js b/fixtures/dom/src/components/fixtures/password-inputs/index.js index 7c4094534bdad..bec39919781bf 100644 --- a/fixtures/dom/src/components/fixtures/password-inputs/index.js +++ b/fixtures/dom/src/components/fixtures/password-inputs/index.js @@ -4,30 +4,28 @@ import FixtureSet from '../../FixtureSet'; import TestCase from '../../TestCase'; import PasswordTestCase from './PasswordTestCase' -const NumberInputs = React.createClass({ - render() { - return ( - - - -
  • Type any string (not an actual password
  • -
    +function NumberInputs() { + return ( + + + +
  • Type any string (not an actual password
  • +
    - - The field should include the "unmasking password" icon. - + + The field should include the "unmasking password" icon. + - -
    -
    - ); - }, -}); + +
    +
    + ); +} export default NumberInputs; diff --git a/fixtures/dom/src/components/fixtures/range-inputs/index.js b/fixtures/dom/src/components/fixtures/range-inputs/index.js index e3be8ad2ce546..bea61f453e15b 100644 --- a/fixtures/dom/src/components/fixtures/range-inputs/index.js +++ b/fixtures/dom/src/components/fixtures/range-inputs/index.js @@ -1,12 +1,10 @@ const React = window.React; -const RangeInputs = React.createClass({ - getInitialState() { - return { value: 0.5 }; - }, - onChange(event) { +class RangeInputs extends React.Component { + state = { value: 0.5 }; + onChange = (event) => { this.setState({ value: event.target.value }); - }, + } render() { return (
    @@ -22,7 +20,7 @@ const RangeInputs = React.createClass({
    ); - }, -}); + } +} export default RangeInputs; diff --git a/fixtures/dom/src/components/fixtures/selects/index.js b/fixtures/dom/src/components/fixtures/selects/index.js index c991da7ce381f..a520e40c3b92f 100644 --- a/fixtures/dom/src/components/fixtures/selects/index.js +++ b/fixtures/dom/src/components/fixtures/selects/index.js @@ -1,12 +1,10 @@ const React = window.React; -const SelectFixture = React.createClass({ - getInitialState() { - return { value: '' }; - }, - onChange(event) { +class SelectFixture extends React.Component { + state = { value: '' }; + onChange = (event) => { this.setState({ value: event.target.value }); - }, + } render() { return (
    @@ -31,7 +29,7 @@ const SelectFixture = React.createClass({
    ); - }, -}); + } +} export default SelectFixture; diff --git a/fixtures/dom/src/components/fixtures/text-inputs/index.js b/fixtures/dom/src/components/fixtures/text-inputs/index.js index 2db132bcb1032..a1683672ce66b 100644 --- a/fixtures/dom/src/components/fixtures/text-inputs/index.js +++ b/fixtures/dom/src/components/fixtures/text-inputs/index.js @@ -1,13 +1,11 @@ const React = window.React; -const TextInputFixtures = React.createClass({ - getInitialState() { - return { - color: '#ffaaee', - }; - }, +class TextInputFixtures extends React.Component { + state = { + color: '#ffaaee', + }; - renderControlled(type) { + renderControlled = (type) => { let id = `controlled_${type}`; let onChange = e => { @@ -29,9 +27,9 @@ const TextInputFixtures = React.createClass({   → {JSON.stringify(state)} ); - }, + } - renderUncontrolled(type) { + renderUncontrolled = (type) => { let id = `uncontrolled_${type}`; return (
    @@ -39,7 +37,7 @@ const TextInputFixtures = React.createClass({
    ); - }, + } render() { // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input @@ -60,7 +58,7 @@ const TextInputFixtures = React.createClass({ ); - }, -}); + } +} module.exports = TextInputFixtures; diff --git a/fixtures/dom/src/components/fixtures/textareas/index.js b/fixtures/dom/src/components/fixtures/textareas/index.js index e39b2e2abf7f7..e2508f32b52c5 100644 --- a/fixtures/dom/src/components/fixtures/textareas/index.js +++ b/fixtures/dom/src/components/fixtures/textareas/index.js @@ -1,12 +1,10 @@ const React = window.React; -const TextAreaFixtures = React.createClass({ - getInitialState() { - return { value: '' }; - }, - onChange(event) { +class TextAreaFixtures extends React.Component { + state = { value: '' }; + onChange = (event) => { this.setState({ value: event.target.value }); - }, + } render() { return (
    @@ -30,7 +28,7 @@ const TextAreaFixtures = React.createClass({
    ); - }, -}); + } +} module.exports = TextAreaFixtures; diff --git a/fixtures/dom/src/components/propTypes.js b/fixtures/dom/src/components/propTypes.js index 42f4f4cf3ae27..dfb80d0a99b81 100644 --- a/fixtures/dom/src/components/propTypes.js +++ b/fixtures/dom/src/components/propTypes.js @@ -1,11 +1,10 @@ +import PropTypes from 'prop-types'; import semver from 'semver'; -const React = window.React; - export function semverString (props, propName, componentName) { let version = props[propName]; - let error = React.PropTypes.string(...arguments); + let error = PropTypes.string(...arguments); if (!error && version != null && !semver.valid(version)) error = new Error( `\`${propName}\` should be a valid "semantic version" matching ` + diff --git a/fixtures/dom/yarn.lock b/fixtures/dom/yarn.lock index e45c4a70540d7..762b52cfc093b 100644 --- a/fixtures/dom/yarn.lock +++ b/fixtures/dom/yarn.lock @@ -4239,6 +4239,12 @@ promise@7.1.1, promise@^7.1.1: dependencies: asap "~2.0.3" +prop-types@^15.5.6: + version "15.5.6" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.5.6.tgz#797a915b1714b645ebb7c5d6cc690346205bd2aa" + dependencies: + fbjs "^0.8.9" + proxy-addr@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-1.1.2.tgz#b4cc5f22610d9535824c123aef9d3cf73c40ba37" diff --git a/mocks/ReactElementTestChild.js b/mocks/ReactElementTestChild.js index f2c94d5dd6885..2e9d076933047 100644 --- a/mocks/ReactElementTestChild.js +++ b/mocks/ReactElementTestChild.js @@ -13,10 +13,10 @@ var React = require('React'); -var Child = React.createClass({ - render: function() { +class Child extends React.Component { + render() { return React.createElement('div'); - }, -}); + } +} module.exports = Child; diff --git a/mocks/ReactMockedComponentTestComponent.js b/mocks/ReactMockedComponentTestComponent.js index 903006487374f..285265df14876 100644 --- a/mocks/ReactMockedComponentTestComponent.js +++ b/mocks/ReactMockedComponentTestComponent.js @@ -13,23 +13,18 @@ var React = require('React'); -var ReactMockedComponentTestComponent = React.createClass({ - getDefaultProps: function() { - return {bar: 'baz'}; - }, +class ReactMockedComponentTestComponent extends React.Component { + state = {foo: 'bar'}; - getInitialState: function() { - return {foo: 'bar'}; - }, - - hasCustomMethod: function() { + hasCustomMethod() { return true; - }, + } - render: function() { + render() { return ; - }, + } -}); +} +ReactMockedComponentTestComponent.defaultProps = {bar: 'baz'}; module.exports = ReactMockedComponentTestComponent; diff --git a/package.json b/package.json index 9cc3af3f92680..7dba9499b1739 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "react-build", "private": true, - "version": "16.0.0-alpha.7", + "version": "16.0.0-alpha.9", "devDependencies": { "aliasify": "^2.0.0", "art": "^0.10.1", @@ -43,6 +43,7 @@ "coffee-script": "^1.8.0", "core-js": "^2.2.1", "coveralls": "^2.11.6", + "create-react-class": "^15.5.2", "del": "^2.0.2", "derequire": "^2.0.3", "escape-string-regexp": "^1.0.5", @@ -72,6 +73,7 @@ "object-assign": "^4.1.1", "platform": "^1.1.0", "prettier": "^0.22.0", + "prop-types": "^15.5.6", "rimraf": "^2.6.1", "rollup": "^0.41.6", "rollup-plugin-alias": "^1.2.1", @@ -109,8 +111,8 @@ "jest": { "modulePathIgnorePatterns": [ "/.module-cache/", - "/react/build/", - "/react/scripts/rollup/shims/" + "/build/", + "/scripts/rollup/shims/" ], "rootDir": "", "transform": { diff --git a/packages/react-art/package.json b/packages/react-art/package.json index b550b1e0fba68..5f14174fa795e 100644 --- a/packages/react-art/package.json +++ b/packages/react-art/package.json @@ -1,6 +1,6 @@ { "name": "react-art", - "version": "16.0.0-alpha.7", + "version": "16.0.0-alpha.9", "description": "React ART is a JavaScript library for drawing vector graphics using React. It provides declarative and reactive bindings to the ART library. Using the same declarative API you can render the output to either Canvas, SVG or VML (IE8).", "main": "index.js", "repository": "facebook/react", @@ -23,7 +23,7 @@ "object-assign": "^4.1.0" }, "peerDependencies": { - "react": "^16.0.0-alpha.7" + "react": "^16.0.0-alpha.9" }, "files": [ "LICENSE", diff --git a/packages/react-dom/package.json b/packages/react-dom/package.json index 831a2bed68e5e..52070cd964734 100644 --- a/packages/react-dom/package.json +++ b/packages/react-dom/package.json @@ -1,6 +1,6 @@ { "name": "react-dom", - "version": "16.0.0-alpha.7", + "version": "16.0.0-alpha.9", "description": "React package for working with the DOM.", "main": "index.js", "repository": "facebook/react", @@ -15,10 +15,11 @@ "dependencies": { "fbjs": "^0.8.9", "loose-envify": "^1.1.0", - "object-assign": "^4.1.0" + "object-assign": "^4.1.0", + "prop-types": "^15.5.6" }, "peerDependencies": { - "react": "^16.0.0-alpha.7" + "react": "^16.0.0-alpha.9" }, "files": [ "LICENSE", diff --git a/packages/react-noop-renderer/package.json b/packages/react-noop-renderer/package.json index 0402ad8d006dd..bfe1749a0ccf4 100644 --- a/packages/react-noop-renderer/package.json +++ b/packages/react-noop-renderer/package.json @@ -1,6 +1,6 @@ { "name": "react-noop-renderer", - "version": "16.0.0-alpha.7", + "version": "16.0.0-alpha.9", "private": true, "description": "React package for testing the Fiber reconciler.", "main": "index.js", diff --git a/packages/react-test-renderer/package.json b/packages/react-test-renderer/package.json index eb2b8732f3e4c..a9cedf4ce70d9 100644 --- a/packages/react-test-renderer/package.json +++ b/packages/react-test-renderer/package.json @@ -1,6 +1,6 @@ { "name": "react-test-renderer", - "version": "16.0.0-alpha.7", + "version": "16.0.0-alpha.9", "description": "React package for snapshot testing.", "main": "index.js", "repository": "facebook/react", @@ -19,7 +19,7 @@ "object-assign": "^4.1.0" }, "peerDependencies": { - "react": "^16.0.0-alpha.7" + "react": "^16.0.0-alpha.9" }, "files": [ "LICENSE", diff --git a/packages/react/package.json b/packages/react/package.json index 4ce3cc5b9ade9..4df2dac46893d 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -1,7 +1,7 @@ { "name": "react", "description": "React is a JavaScript library for building user interfaces.", - "version": "16.0.0-alpha.7", + "version": "16.0.0-alpha.9", "keywords": [ "react" ], @@ -22,9 +22,11 @@ "node": ">=0.10.0" }, "dependencies": { + "create-react-class": "^15.5.2", "fbjs": "^0.8.9", "loose-envify": "^1.1.0", - "object-assign": "^4.1.0" + "object-assign": "^4.1.0", + "prop-types": "^15.5.6" }, "browserify": { "transform": [ diff --git a/scripts/fiber/tests-passing.txt b/scripts/fiber/tests-passing.txt index 0c658781d6468..0cf8d024093b5 100644 --- a/scripts/fiber/tests-passing.txt +++ b/scripts/fiber/tests-passing.txt @@ -44,6 +44,9 @@ scripts/shared/__tests__/evalToString-test.js src/isomorphic/__tests__/React-test.js * should log a deprecation warning once when using React.createMixin +* should warn once when attempting to access React.createClass +* should warn once when attempting to access React.PropTypes +* should warn once when attempting to access React.checkPropTypes src/isomorphic/children/__tests__/ReactChildren-test.js * should support identity for simple @@ -89,23 +92,8 @@ src/isomorphic/classic/__tests__/ReactContextValidator-test.js * should warn (but not error) if getChildContext method is missing * should pass parent context if getChildContext method is missing -src/isomorphic/classic/class/__tests__/ReactBind-test.js -* Holds reference to instance -* works with mixins -* warns if you try to bind to this -* does not warn if you pass an auto-bound method to setState - -src/isomorphic/classic/class/__tests__/ReactBindOptout-test.js -* should work with manual binding -* should not hold reference to instance -* works with mixins that have not opted out of autobinding -* works with mixins that have opted out of autobinding -* does not warn if you try to bind to this -* does not warn if you pass an manually bound method to setState - -src/isomorphic/classic/class/__tests__/ReactClass-test.js +src/isomorphic/classic/class/__tests__/create-react-class-integration-test.js * should throw when `render` is not specified -* should copy `displayName` onto the Constructor * should copy prop types onto the Constructor * should warn on invalid prop types * should warn on invalid context types @@ -119,34 +107,13 @@ src/isomorphic/classic/class/__tests__/ReactClass-test.js * should throw with non-object getInitialState() return values * should work with a null getInitialState() return value * should throw when using legacy factories - -src/isomorphic/classic/class/__tests__/ReactClassMixin-test.js -* should support merging propTypes and statics -* should support chaining delegate functions -* should chain functions regardless of spec property order -* should validate prop types via mixins -* should override mixin prop types with class prop types -* should support mixins with getInitialState() -* should throw with conflicting getInitialState() methods -* should not mutate objects returned by getInitialState() -* should support statics in mixins -* should throw if mixins override each others' statics -* should throw if mixins override functions in statics -* should warn if the mixin is undefined -* should warn if the mixin is null -* should warn if an undefined mixin is included in another mixin -* should warn if a null mixin is included in another mixin -* should throw if the mixin is a React component -* should throw if the mixin is a React component class -* should have bound the mixin methods to the component -* should include the mixin keys in even if their values are falsy -* should work with a null getInitialState return value and a mixin +* replaceState and callback works +* isMounted works src/isomorphic/classic/element/__tests__/ReactElement-test.js * uses the fallback value when in an environment without Symbol * returns a complete element according to spec -* should warn when `key` is being accessed on createClass element -* should warn when `key` is being accessed on ES class element +* should warn when `key` is being accessed on composite element * should warn when `key` is being accessed on a host element * should warn when `ref` is being accessed * allows a string to be passed as the type @@ -165,7 +132,6 @@ src/isomorphic/classic/element/__tests__/ReactElement-test.js * merges rest arguments onto the children prop in an array * allows static methods to be called using the type property * identifies valid elements -* allows the use of PropTypes validators in statics * is indistinguishable from a plain object * should use default prop value when removing a prop * should normalize props with default values @@ -314,19 +280,6 @@ src/isomorphic/classic/types/__tests__/ReactPropTypes-test.js * should have received the validator's return value * should not warn if the validator returned null -src/isomorphic/classic/types/__tests__/ReactPropTypesProduction-test.js -* should be a no-op -* should be a no-op -* should be a no-op -* should be a no-op -* should be a no-op -* should be a no-op -* should be a no-op -* should be a no-op -* should be a no-op -* should be a no-op -* should not have been called - src/isomorphic/modern/class/__tests__/ReactClassEquivalence-test.js * tests the same thing for es6 classes and CoffeeScript * tests the same thing for es6 classes and TypeScript @@ -495,8 +448,6 @@ src/renderers/__tests__/ReactCompositeComponent-test.js * should react to state changes from callbacks * should rewire refs when rendering to different child types * should not cache old DOM nodes when switching constructors -* should auto bind methods and values correctly -* should not pass this to getDefaultProps * should use default values for undefined props * should not mutate passed-in props object * should warn about `forceUpdate` on unmounted components @@ -718,7 +669,6 @@ src/renderers/__tests__/ReactUpdates-test.js * does not call render after a component as been deleted * marks top-level updates * throws in setState if the update callback is not a function -* throws in replaceState if the update callback is not a function * throws in forceUpdate if the update callback is not a function * does not update one component twice in a batch (#2410) * does not update one component twice in a batch (#6371) @@ -924,6 +874,7 @@ src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js * should work error event on element * should not duplicate uppercased selfclosing tags * should warn on upper case HTML tags, not SVG nor custom tags +* should warn if the tag is unrecognized * should warn against children for void elements * should warn against dangerouslySetInnerHTML for void elements * should include owner rather than parent in warnings @@ -1299,7 +1250,6 @@ src/renderers/dom/shared/__tests__/ReactServerRendering-test.js * allows setState in componentWillMount without using DOM * renders components with different batching strategies * warns with a no-op when an async setState is triggered -* warns with a no-op when an async replaceState is triggered * warns with a no-op when an async forceUpdate is triggered * should warn when children are mutated during render diff --git a/scripts/jest/ts-preprocessor.js b/scripts/jest/ts-preprocessor.js index b4fe74552c40f..29ad307a9a392 100644 --- a/scripts/jest/ts-preprocessor.js +++ b/scripts/jest/ts-preprocessor.js @@ -23,7 +23,7 @@ function compile(content, contentFilename) { getSourceFile(filename, languageVersion) { var source; var jestRegex = /jest\.d\.ts/; - var reactRegex = /(?:React|ReactDOM)(?:\.d)?\.ts$/; + var reactRegex = /(?:React|ReactDOM|PropTypes)(?:\.d)?\.ts$/; // `path.normalize` is used to turn forward slashes in // the file path into backslashes on Windows. diff --git a/scripts/rollup/build.js b/scripts/rollup/build.js index 30d8d29e9a874..7f3055b099239 100644 --- a/scripts/rollup/build.js +++ b/scripts/rollup/build.js @@ -31,8 +31,12 @@ const RN_DEV = Bundles.bundleTypes.RN_DEV; const RN_PROD = Bundles.bundleTypes.RN_PROD; const reactVersion = require('../../package.json').version; -const inputBundleType = argv.type; -const inputBundleName = argv._ && argv._[0]; +const requestedBundleTypes = (argv.type || '') + .split(',') + .map(type => type.toUpperCase()); +const requestedBundleNames = (argv._[0] || '') + .split(',') + .map(type => type.toLowerCase()); // used for when we property mangle with uglify/gcc const mangleRegex = new RegExp( @@ -242,12 +246,19 @@ function getPlugins( manglePropertiesOnProd ) { const plugins = [ - replace(Modules.getDefaultReplaceModules(bundleType)), babel(updateBabelConfig(babelOpts, bundleType)), alias( Modules.getAliases(paths, bundleType, isRenderer, argv.extractErrors) ), ]; + + const replaceModules = Modules.getDefaultReplaceModules(bundleType); + // We have to do this check because Rollup breaks on empty object. + // TODO: file an issue with rollup-plugin-replace. + if (Object.keys(replaceModules).length > 0) { + plugins.unshift(replace(replaceModules)); + } + switch (bundleType) { case UMD_DEV: case NODE_DEV: @@ -294,14 +305,26 @@ function getPlugins( } function createBundle(bundle, bundleType) { - if ( - (inputBundleType && bundleType.indexOf(inputBundleType) === -1) || - bundle.bundleTypes.indexOf(bundleType) === -1 || - (inputBundleName && bundle.label.indexOf(inputBundleName) === -1) - ) { - // Skip this bundle because its config doesn't specify this target. + const shouldSkipBundleType = bundle.bundleTypes.indexOf(bundleType) === -1; + if (shouldSkipBundleType) { return Promise.resolve(); } + if (requestedBundleTypes.length > 0) { + const isAskingForDifferentType = requestedBundleTypes.every( + requestedType => bundleType.indexOf(requestedType) === -1 + ); + if (isAskingForDifferentType) { + return Promise.resolve(); + } + } + if (requestedBundleNames.length > 0) { + const isAskingForDifferentNames = requestedBundleNames.every( + requestedName => bundle.label.indexOf(requestedName) === -1 + ); + if (isAskingForDifferentNames) { + return Promise.resolve(); + } + } const filename = getFilename(bundle.name, bundle.hasteName, bundleType); const logKey = chalk.white.bold(filename) + diff --git a/scripts/rollup/bundles.js b/scripts/rollup/bundles.js index a9010f0d0db35..cc33df8d123e8 100644 --- a/scripts/rollup/bundles.js +++ b/scripts/rollup/bundles.js @@ -44,8 +44,13 @@ const bundles = [ moduleName: 'React', sourceMap: false, }, - entry: 'src/umd/ReactUMDEntry.js', - externals: [], + entry: 'src/isomorphic/React.js', + externals: [ + 'create-react-class/factory', + 'prop-types', + 'prop-types/checkPropTypes', + 'prop-types/factory', + ], fbEntry: 'src/fb/ReactFBEntry.js', hasteName: 'React', isRenderer: false, @@ -53,9 +58,6 @@ const bundles = [ manglePropertiesOnProd: false, name: 'react', paths: [ - 'src/umd/ReactUMDEntry.js', - 'src/umd/shims/**/*.js', - 'src/isomorphic/**/*.js', 'src/addons/**/*.js', @@ -76,8 +78,8 @@ const bundles = [ moduleName: 'ReactDOM', sourceMap: false, }, - entry: 'src/umd/ReactDOMUMDEntry.js', - externals: [], + entry: 'src/renderers/dom/ReactDOM.js', + externals: ['prop-types', 'prop-types/checkPropTypes'], fbEntry: 'src/fb/ReactDOMFBEntry.js', hasteName: 'ReactDOMStack', isRenderer: true, @@ -85,12 +87,11 @@ const bundles = [ manglePropertiesOnProd: false, name: 'react-dom-stack', paths: [ - 'src/umd/ReactDOMUMDEntry.js', - 'src/renderers/dom/**/*.js', 'src/renderers/shared/**/*.js', 'src/test/**/*.js', // ReactTestUtils is currently very coupled to DOM. + 'src/isomorphic/classic/types/checkPropTypes.js', 'src/ReactVersion.js', 'src/shared/**/*.js', ], @@ -106,8 +107,8 @@ const bundles = [ moduleName: 'ReactDOM', sourceMap: false, }, - entry: 'src/umd/ReactDOMUMDEntry.js', - externals: [], + entry: 'src/renderers/dom/fiber/ReactDOMFiber.js', + externals: ['prop-types', 'prop-types/checkPropTypes'], fbEntry: 'src/fb/ReactDOMFiberFBEntry.js', hasteName: 'ReactDOMFiber', isRenderer: true, @@ -115,12 +116,11 @@ const bundles = [ manglePropertiesOnProd: false, name: 'react-dom', paths: [ - 'src/umd/ReactDOMUMDEntry.js', - 'src/renderers/dom/**/*.js', 'src/renderers/shared/**/*.js', 'src/test/**/*.js', // ReactTestUtils is currently very coupled to DOM. + 'src/isomorphic/classic/types/checkPropTypes.js', 'src/ReactVersion.js', 'src/shared/**/*.js', ], @@ -139,20 +139,19 @@ const bundles = [ moduleName: 'ReactDOMServer', sourceMap: false, }, - entry: 'src/umd/ReactDOMServerUMDEntry.js', - externals: [], - fbEntry: 'src/umd/ReactDOMServerUMDEntry.js', + entry: 'src/renderers/dom/ReactDOMServer.js', + externals: ['prop-types', 'prop-types/checkPropTypes'], + fbEntry: 'src/renderers/dom/ReactDOMServer.js', hasteName: 'ReactDOMServerStack', isRenderer: true, label: 'dom-server', manglePropertiesOnProd: false, name: 'react-dom/server', paths: [ - 'src/umd/ReactDOMServerUMDEntry.js', - 'src/renderers/dom/**/*.js', 'src/renderers/shared/**/*.js', + 'src/isomorphic/classic/types/checkPropTypes.js', 'src/ReactVersion.js', 'src/shared/**/*.js', ], @@ -178,6 +177,8 @@ const bundles = [ 'art/modes/current', 'art/modes/fast-noSideEffects', 'art/core/transform', + 'prop-types/checkPropTypes', + 'react-dom', ], fbEntry: 'src/renderers/art/ReactARTStack.js', hasteName: 'ReactARTStack', @@ -186,11 +187,10 @@ const bundles = [ manglePropertiesOnProd: false, name: 'react-art', paths: [ - // TODO: it relies on ReactDOMFrameScheduling. Need to move to shared/? - 'src/renderers/dom/**/*.js', 'src/renderers/art/**/*.js', 'src/renderers/shared/**/*.js', + 'src/isomorphic/classic/types/checkPropTypes.js', 'src/ReactVersion.js', 'src/shared/**/*.js', ], @@ -213,6 +213,8 @@ const bundles = [ 'art/modes/current', 'art/modes/fast-noSideEffects', 'art/core/transform', + 'prop-types/checkPropTypes', + 'react-dom', ], fbEntry: 'src/renderers/art/ReactARTFiber.js', hasteName: 'ReactARTFiber', @@ -221,11 +223,10 @@ const bundles = [ manglePropertiesOnProd: false, name: 'react-art', paths: [ - // TODO: it relies on ReactDOMFrameScheduling. Need to move to shared/? - 'src/renderers/dom/**/*.js', 'src/renderers/art/**/*.js', 'src/renderers/shared/**/*.js', + 'src/isomorphic/classic/types/checkPropTypes.js', 'src/ReactVersion.js', 'src/shared/**/*.js', ], @@ -315,7 +316,7 @@ const bundles = [ sourceMap: false, }, entry: 'src/renderers/testing/ReactTestRendererFiber', - externals: [], + externals: ['prop-types/checkPropTypes'], fbEntry: 'src/renderers/testing/ReactTestRendererFiber', hasteName: 'ReactTestRendererFiber', isRenderer: true, @@ -327,6 +328,7 @@ const bundles = [ 'src/renderers/shared/**/*.js', 'src/renderers/testing/**/*.js', + 'src/isomorphic/classic/types/checkPropTypes.js', 'src/ReactVersion.js', 'src/shared/**/*.js', ], @@ -341,7 +343,7 @@ const bundles = [ sourceMap: false, }, entry: 'src/renderers/testing/stack/ReactTestRendererStack', - externals: [], + externals: ['prop-types/checkPropTypes'], fbEntry: 'src/renderers/testing/stack/ReactTestRendererStack', hasteName: 'ReactTestRendererStack', isRenderer: true, @@ -353,6 +355,7 @@ const bundles = [ 'src/renderers/shared/**/*.js', 'src/renderers/testing/**/*.js', + 'src/isomorphic/classic/types/checkPropTypes.js', 'src/ReactVersion.js', 'src/shared/**/*.js', ], @@ -371,7 +374,7 @@ const bundles = [ sourceMap: false, }, entry: 'src/renderers/noop/ReactNoop.js', - externals: [], + externals: ['prop-types/checkPropTypes'], isRenderer: true, label: 'noop-fiber', manglePropertiesOnProd: false, @@ -380,6 +383,7 @@ const bundles = [ 'src/renderers/noop/**/*.js', 'src/renderers/shared/**/*.js', + 'src/isomorphic/classic/types/checkPropTypes.js', 'src/ReactVersion.js', 'src/shared/**/*.js', ], diff --git a/scripts/rollup/modules.js b/scripts/rollup/modules.js index 2be3391469332..985fe8869087a 100644 --- a/scripts/rollup/modules.js +++ b/scripts/rollup/modules.js @@ -46,10 +46,18 @@ const fbjsModules = [ ]; const devOnlyFilesToStubOut = [ + "'ReactDebugCurrentFrame'", "'ReactComponentTreeHook'", - "'react/lib/ReactComponentTreeHook'", - "'react-dom/lib/ReactPerf'", - "'react-dom/lib/ReactTestUtils'", + "'ReactPerf'", + "'ReactTestUtils'", +]; + +const legacyModules = [ + 'create-react-class', + 'create-react-class/factory', + 'prop-types', + 'prop-types/checkPropTypes', + 'prop-types/factory', ]; // this function builds up a very niave Haste-like moduleMap @@ -112,7 +120,6 @@ function ignoreFBModules() { 'ReactDOMFeatureFlags', // In FB bundles, we preserve an inline require to ReactCurrentOwner. // See the explanation in FB version of ReactCurrentOwner in www: - 'react/lib/ReactCurrentOwner', 'ReactCurrentOwner', ]; } @@ -155,7 +162,7 @@ function getExternalModules(externals, bundleType, isRenderer) { case FB_DEV: case FB_PROD: fbjsModules.forEach(module => externalModules.push(module)); - externalModules.push('react/lib/ReactCurrentOwner', 'ReactCurrentOwner'); + externalModules.push('ReactCurrentOwner'); if (isRenderer) { externalModules.push('React'); } @@ -169,19 +176,6 @@ function getInternalModules() { // it doesn't pick them up and assumes they're external return { reactProdInvariant: resolve('./src/shared/utils/reactProdInvariant.js'), - 'react/lib/ReactDebugCurrentFrame': resolve( - './src/isomorphic/classic/element/ReactDebugCurrentFrame.js' - ), - }; -} - -function replaceInternalModules() { - // we inline these modules in the bundles rather than leave them as external - return { - "'react-dom/lib/ReactPerf'": `'${resolve('./src/renderers/shared/ReactPerf.js')}'`, - "'react-dom/lib/ReactTestUtils'": `'${resolve('./src/test/ReactTestUtils.js')}'`, - "'react-dom/lib/ReactInstanceMap'": `'${resolve('./src/renderers/shared/shared/ReactInstanceMap.js')}'`, - "'react-dom'": `'${resolve('./src/renderers/dom/ReactDOM.js')}'`, }; } @@ -210,92 +204,16 @@ function getFbjsModuleAliases(bundleType) { function replaceFbjsModuleAliases(bundleType) { switch (bundleType) { - case UMD_DEV: - case UMD_PROD: - case NODE_DEV: - case NODE_PROD: - case RN_DEV: - case RN_PROD: - return {}; case FB_DEV: case FB_PROD: - // additionally we add mappings for "react" - // so they work correctly on FB, this will change soon + // Haste at FB doesn't currently allow case sensitive names, + // and product code already uses "React". In the future, + // we will either allow both variants or migrate to lowercase. return { "'react'": "'React'", }; - } -} - -// for renderers, we want them to require the __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner -// on the React bundle itself rather than require module directly. -// For the React bundle, ReactCurrentOwner should be bundled as part of the bundle -// itself and exposed on __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED -const shimReactCurrentOwner = resolve( - './scripts/rollup/shims/rollup/ReactCurrentOwnerRollupShim.js' -); -const realReactCurrentOwner = resolve( - './src/isomorphic/classic/element/ReactCurrentOwner.js' -); - -function getReactCurrentOwnerModuleAlias(bundleType, isRenderer) { - if (bundleType === FB_DEV || bundleType === FB_DEV) { - return {}; - } - if (isRenderer) { - return { - ReactCurrentOwner: shimReactCurrentOwner, - 'react/lib/ReactCurrentOwner': shimReactCurrentOwner, - }; - } else { - return { - ReactCurrentOwner: realReactCurrentOwner, - 'react/lib/ReactCurrentOwner': realReactCurrentOwner, - }; - } -} - -// this works almost identically to the ReactCurrentOwner shim above -const shimReactCheckPropTypes = resolve( - './scripts/rollup/shims/rollup/ReactCheckPropTypesRollupShim.js' -); -const realCheckPropTypes = resolve( - './src/isomorphic/classic/types/checkPropTypes.js' -); - -function getReactCheckPropTypesModuleAlias(bundleType, isRenderer) { - if (isRenderer) { - return { - checkPropTypes: shimReactCheckPropTypes, - 'react/lib/checkPropTypes': shimReactCheckPropTypes, - }; - } else { - return { - checkPropTypes: realCheckPropTypes, - 'react/lib/checkPropTypes': realCheckPropTypes, - }; - } -} - -// this works almost identically to the ReactCurrentOwner shim above -const shimReactComponentTreeHook = resolve( - './scripts/rollup/shims/rollup/ReactComponentTreeHookRollupShim.js' -); -const realReactComponentTreeHook = resolve( - './src/isomorphic/hooks/ReactComponentTreeHook.js' -); - -function getReactComponentTreeHookModuleAlias(bundleType, isRenderer) { - if (isRenderer) { - return { - ReactComponentTreeHook: shimReactComponentTreeHook, - 'react/lib/ReactComponentTreeHook': shimReactComponentTreeHook, - }; - } else { - return { - ReactComponentTreeHook: realReactComponentTreeHook, - 'react/lib/ReactComponentTreeHook': realReactComponentTreeHook, - }; + default: + return {}; } } @@ -320,11 +238,31 @@ function replaceDevOnlyStubbedModules(bundleType) { } } +function replaceLegacyModuleAliases(bundleType) { + switch (bundleType) { + case UMD_DEV: + case UMD_PROD: + const modulesAlias = {}; + legacyModules.forEach(legacyModule => { + const modulePath = legacyModule.includes('/') + ? legacyModule + : `${legacyModule}/index`; + const resolvedPath = resolve(`./node_modules/${modulePath}`); + modulesAlias[`'${legacyModule}'`] = `'${resolvedPath}'`; + }); + return modulesAlias; + case NODE_DEV: + case NODE_PROD: + case FB_DEV: + case FB_PROD: + case RN_DEV: + case RN_PROD: + return {}; + } +} + function getAliases(paths, bundleType, isRenderer, extractErrors) { return Object.assign( - getReactCurrentOwnerModuleAlias(bundleType, isRenderer), - getReactCheckPropTypesModuleAlias(bundleType, isRenderer), - getReactComponentTreeHookModuleAlias(bundleType, isRenderer), createModuleMap( paths, extractErrors && extractErrorCodes(errorCodeOpts), @@ -339,9 +277,9 @@ function getAliases(paths, bundleType, isRenderer, extractErrors) { function getDefaultReplaceModules(bundleType) { return Object.assign( {}, - replaceInternalModules(), replaceFbjsModuleAliases(bundleType), - replaceDevOnlyStubbedModules(bundleType) + replaceDevOnlyStubbedModules(bundleType), + replaceLegacyModuleAliases(bundleType) ); } @@ -353,17 +291,7 @@ module.exports = { getExcludedHasteGlobs, getDefaultReplaceModules, getAliases, - createModuleMap, - getNodeModules, - replaceInternalModules, - getInternalModules, - getFbjsModuleAliases, - replaceFbjsModuleAliases, ignoreFBModules, ignoreReactNativeModules, getExternalModules, - getReactCurrentOwnerModuleAlias, - getReactCheckPropTypesModuleAlias, - getReactComponentTreeHookModuleAlias, - replaceDevOnlyStubbedModules, }; diff --git a/scripts/rollup/results.json b/scripts/rollup/results.json index f2b0466682d6b..01bee146eedc6 100644 --- a/scripts/rollup/results.json +++ b/scripts/rollup/results.json @@ -2,116 +2,116 @@ "branch": "master", "bundleSizes": { "react.development.js (UMD_DEV)": { - "size": 116402, - "gzip": 29598 + "size": 121454, + "gzip": 30515 }, "react.production.min.js (UMD_PROD)": { - "size": 13719, - "gzip": 5080 + "size": 15685, + "gzip": 5765 }, "react-dom.development.js (UMD_DEV)": { - "size": 564925, - "gzip": 130471 + "size": 583190, + "gzip": 134534 }, "react-dom.production.min.js (UMD_PROD)": { - "size": 120123, - "gzip": 37844 + "size": 120740, + "gzip": 38094 }, "react-dom-server.development.js (UMD_DEV)": { - "size": 477377, - "gzip": 115561 + "size": 495558, + "gzip": 119685 }, "react-dom-server.production.min.js (UMD_PROD)": { - "size": 106349, - "gzip": 32998 + "size": 107033, + "gzip": 33273 }, "react-art.development.js (UMD_DEV)": { - "size": 341151, - "gzip": 76261 + "size": 342608, + "gzip": 76782 }, "react-art.production.min.js (UMD_PROD)": { - "size": 94939, - "gzip": 28976 + "size": 95013, + "gzip": 28991 }, "react.development.js (NODE_DEV)": { - "size": 109416, - "gzip": 27554 + "size": 70266, + "gzip": 17594 }, "react.production.min.js (NODE_PROD)": { - "size": 12615, - "gzip": 4659 + "size": 9226, + "gzip": 3628 }, "React-dev.js (FB_DEV)": { - "size": 110901, - "gzip": 28115 + "size": 72123, + "gzip": 18231 }, "React-prod.js (FB_PROD)": { - "size": 56205, - "gzip": 14329 + "size": 36643, + "gzip": 9256 }, "ReactDOMStack-dev.js (FB_DEV)": { - "size": 523568, - "gzip": 124900 + "size": 522763, + "gzip": 124727 }, "ReactDOMStack-prod.js (FB_PROD)": { - "size": 351707, - "gzip": 84367 + "size": 352776, + "gzip": 84675 }, "react-dom.development.js (NODE_DEV)": { - "size": 543299, - "gzip": 125435 + "size": 542188, + "gzip": 125158 }, "react-dom.production.min.js (NODE_PROD)": { - "size": 116802, - "gzip": 36707 + "size": 116925, + "gzip": 36732 }, "ReactDOMFiber-dev.js (FB_DEV)": { - "size": 797779, - "gzip": 184190 + "size": 797235, + "gzip": 184122 }, "ReactDOMFiber-prod.js (FB_PROD)": { - "size": 407360, - "gzip": 93460 + "size": 407613, + "gzip": 93586 }, "react-dom-server.development.js (NODE_DEV)": { - "size": 446778, - "gzip": 107862 + "size": 445589, + "gzip": 107597 }, "react-dom-server.production.min.js (NODE_PROD)": { - "size": 101204, - "gzip": 31227 + "size": 101411, + "gzip": 31292 }, "ReactDOMServerStack-dev.js (FB_DEV)": { - "size": 445529, - "gzip": 107719 + "size": 444281, + "gzip": 107443 }, "ReactDOMServerStack-prod.js (FB_PROD)": { - "size": 332974, - "gzip": 80219 + "size": 334166, + "gzip": 80444 }, "ReactARTStack-dev.js (FB_DEV)": { - "size": 494340, - "gzip": 118840 + "size": 142986, + "gzip": 32714 }, "ReactARTStack-prod.js (FB_PROD)": { - "size": 365115, - "gzip": 87595 + "size": 101143, + "gzip": 22993 }, "react-art.development.js (NODE_DEV)": { - "size": 266386, - "gzip": 57183 + "size": 265052, + "gzip": 56927 }, "react-art.production.min.js (NODE_PROD)": { - "size": 56517, - "gzip": 17109 + "size": 56628, + "gzip": 17152 }, "ReactARTFiber-dev.js (FB_DEV)": { - "size": 265561, - "gzip": 57011 + "size": 264230, + "gzip": 56736 }, "ReactARTFiber-prod.js (FB_PROD)": { - "size": 205194, - "gzip": 43039 + "size": 205336, + "gzip": 43154 }, "ReactNativeStack.js (RN)": { "size": 233993, @@ -122,20 +122,20 @@ "gzip": 84001 }, "ReactTestRendererFiber-dev.js (FB_DEV)": { - "size": 263452, - "gzip": 55974 + "size": 262139, + "gzip": 55704 }, "ReactTestRendererStack-dev.js (FB_DEV)": { - "size": 155222, - "gzip": 35662 + "size": 151521, + "gzip": 34765 }, "react-noop-renderer.development.js (NODE_DEV)": { - "size": 255447, - "gzip": 53933 + "size": 254136, + "gzip": 53682 }, "react-test-renderer.development.js (NODE_DEV)": { - "size": 264286, - "gzip": 56152 + "size": 262970, + "gzip": 55891 } } } \ No newline at end of file diff --git a/scripts/rollup/shims/rollup/ReactCheckPropTypesRollupShim.js b/scripts/rollup/shims/rollup/ReactCheckPropTypesRollupShim.js deleted file mode 100644 index f8866b76e6f1d..0000000000000 --- a/scripts/rollup/shims/rollup/ReactCheckPropTypesRollupShim.js +++ /dev/null @@ -1,3 +0,0 @@ -var React = require('react'); - -module.exports = React.checkPropTypes; diff --git a/scripts/rollup/shims/rollup/ReactCurrentOwnerRollupShim.js b/scripts/rollup/shims/rollup/ReactCurrentOwnerRollupShim.js deleted file mode 100644 index 3e5579b86c5af..0000000000000 --- a/scripts/rollup/shims/rollup/ReactCurrentOwnerRollupShim.js +++ /dev/null @@ -1,3 +0,0 @@ -var ReactInternals = require('react').__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED; - -module.exports = ReactInternals.ReactCurrentOwner; diff --git a/scripts/rollup/shims/rollup/ReactComponentTreeHookRollupShim.js b/scripts/rollup/shims/rollup/ReactDebugCurrentFrameRollupShim.js similarity index 61% rename from scripts/rollup/shims/rollup/ReactComponentTreeHookRollupShim.js rename to scripts/rollup/shims/rollup/ReactDebugCurrentFrameRollupShim.js index bcdc4d8787409..2174fb0166844 100644 --- a/scripts/rollup/shims/rollup/ReactComponentTreeHookRollupShim.js +++ b/scripts/rollup/shims/rollup/ReactDebugCurrentFrameRollupShim.js @@ -1,3 +1,3 @@ var ReactInternals = require('react').__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED; -module.exports = ReactInternals.ReactComponentTreeHook; +module.exports = ReactInternals.ReactDebugCurrentFrame; diff --git a/src/ReactVersion.js b/src/ReactVersion.js index 9df23ee824959..fd32524d89f46 100644 --- a/src/ReactVersion.js +++ b/src/ReactVersion.js @@ -11,4 +11,4 @@ 'use strict'; -module.exports = '16.0.0-alpha.7'; +module.exports = '16.0.0-alpha.9'; diff --git a/src/fb/ReactDOMFBEntry.js b/src/fb/ReactDOMFBEntry.js index 7545c5ce3feec..754febaf3637a 100644 --- a/src/fb/ReactDOMFBEntry.js +++ b/src/fb/ReactDOMFBEntry.js @@ -11,7 +11,7 @@ var ReactDOM = require('ReactDOM'); -ReactDOM.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED = { +Object.assign(ReactDOM.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED, { // These should be easy to copy into react_contrib and remove from here: adler32: require('adler32'), getVendorPrefixedEventName: require('getVendorPrefixedEventName'), @@ -29,25 +29,24 @@ ReactDOM.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED = { SyntheticKeyboardEvent: require('SyntheticKeyboardEvent'), SyntheticMouseEvent: require('SyntheticMouseEvent'), // These are real internal dependencies that are trickier to remove: - EventPluginHub: require('EventPluginHub'), ReactBrowserEventEmitter: require('ReactBrowserEventEmitter'), ReactErrorUtils: require('ReactErrorUtils'), ReactDOMComponentTree: require('ReactDOMComponentTree'), - ReactInstanceMap: require('react-dom/lib/ReactInstanceMap'), + ReactInstanceMap: require('ReactInstanceMap'), // This is used for ajaxify on www: DOMProperty: require('DOMProperty'), // These are dependencies of TapEventPlugin: EventPluginUtils: require('EventPluginUtils'), EventPropagators: require('EventPropagators'), SyntheticUIEvent: require('SyntheticUIEvent'), -}; +}); if (__DEV__) { Object.assign(ReactDOM.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED, { // ReactPerf and ReactTestUtils currently only work with the DOM renderer // so we expose them from here, but only in DEV mode. - ReactPerf: require('react-dom/lib/ReactPerf'), - ReactTestUtils: require('react-dom/lib/ReactTestUtils'), + ReactPerf: require('ReactPerf'), + ReactTestUtils: require('ReactTestUtils'), }); } diff --git a/src/fb/ReactDOMFiberFBEntry.js b/src/fb/ReactDOMFiberFBEntry.js index c019d38c00424..188b9c71248ca 100644 --- a/src/fb/ReactDOMFiberFBEntry.js +++ b/src/fb/ReactDOMFiberFBEntry.js @@ -11,37 +11,39 @@ var ReactDOMFiber = require('ReactDOMFiber'); -ReactDOMFiber.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED = { - // These should be easy to copy into react_contrib and remove from here: - adler32: require('adler32'), - getVendorPrefixedEventName: require('getVendorPrefixedEventName'), - getEventCharCode: require('getEventCharCode'), - getEventKey: require('getEventKey'), - getEventTarget: require('getEventTarget'), - isEventSupported: require('isEventSupported'), - setInnerHTML: require('setInnerHTML'), - setTextContent: require('setTextContent'), - PooledClass: require('PooledClass'), - ReactDOMSelection: require('ReactDOMSelection'), - ReactInputSelection: require('ReactInputSelection'), - // These are mostly used in incorrect Flow typings and are codemoddable: - SyntheticEvent: require('SyntheticEvent'), - SyntheticKeyboardEvent: require('SyntheticKeyboardEvent'), - SyntheticMouseEvent: require('SyntheticMouseEvent'), - // These are real internal dependencies that are trickier to remove: - EventPluginHub: require('EventPluginHub'), - ReactBrowserEventEmitter: require('ReactBrowserEventEmitter'), - ReactErrorUtils: require('ReactErrorUtils'), - ReactFiberErrorLogger: require('ReactFiberErrorLogger'), - ReactDOMComponentTree: require('ReactDOMComponentTree'), - ReactInstanceMap: require('react-dom/lib/ReactInstanceMap'), - // This is used for ajaxify on www: - DOMProperty: require('DOMProperty'), - // These are dependencies of TapEventPlugin: - EventPluginUtils: require('EventPluginUtils'), - EventPropagators: require('EventPropagators'), - SyntheticUIEvent: require('SyntheticUIEvent'), -}; +Object.assign( + ReactDOMFiber.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED, + { + // These should be easy to copy into react_contrib and remove from here: + adler32: require('adler32'), + getVendorPrefixedEventName: require('getVendorPrefixedEventName'), + getEventCharCode: require('getEventCharCode'), + getEventKey: require('getEventKey'), + getEventTarget: require('getEventTarget'), + isEventSupported: require('isEventSupported'), + setInnerHTML: require('setInnerHTML'), + setTextContent: require('setTextContent'), + PooledClass: require('PooledClass'), + ReactDOMSelection: require('ReactDOMSelection'), + ReactInputSelection: require('ReactInputSelection'), + // These are mostly used in incorrect Flow typings and are codemoddable: + SyntheticEvent: require('SyntheticEvent'), + SyntheticKeyboardEvent: require('SyntheticKeyboardEvent'), + SyntheticMouseEvent: require('SyntheticMouseEvent'), + // These are real internal dependencies that are trickier to remove: + ReactBrowserEventEmitter: require('ReactBrowserEventEmitter'), + ReactErrorUtils: require('ReactErrorUtils'), + ReactFiberErrorLogger: require('ReactFiberErrorLogger'), + ReactDOMComponentTree: require('ReactDOMComponentTree'), + ReactInstanceMap: require('ReactInstanceMap'), + // This is used for ajaxify on www: + DOMProperty: require('DOMProperty'), + // These are dependencies of TapEventPlugin: + EventPluginUtils: require('EventPluginUtils'), + EventPropagators: require('EventPropagators'), + SyntheticUIEvent: require('SyntheticUIEvent'), + }, +); if (__DEV__) { Object.assign( @@ -49,7 +51,7 @@ if (__DEV__) { { // ReactPerf and ReactTestUtils currently only work with the DOM renderer // so we expose them from here, but only in DEV mode. - ReactTestUtils: require('react-dom/lib/ReactTestUtils'), + ReactTestUtils: require('ReactTestUtils'), }, ); } diff --git a/src/fb/ReactFBEntry.js b/src/fb/ReactFBEntry.js index 74add5c91bc8b..ac3fe41e622d9 100644 --- a/src/fb/ReactFBEntry.js +++ b/src/fb/ReactFBEntry.js @@ -11,26 +11,12 @@ var React = require('React'); -// `version` will be added here by the React module. -var ReactFBEntry = Object.assign( - { - __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: { - ReactChildren: require('ReactChildren'), - getComponentName: require('getComponentName'), - flattenChildren: require('flattenChildren'), - }, - }, - React, -); +// Add existing internal dependencies from www codebase. +// The goal is to get rid of these with time or turn them into public APIs. +Object.assign(React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED, { + ReactChildren: require('ReactChildren'), + getComponentName: require('getComponentName'), + flattenChildren: require('flattenChildren'), +}); -if (__DEV__) { - Object.assign( - ReactFBEntry.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED, - { - // ReactComponentTreeHook should not be included in production. - ReactComponentTreeHook: require('react/lib/ReactComponentTreeHook'), - }, - ); -} - -module.exports = ReactFBEntry; +module.exports = React; diff --git a/src/isomorphic/React.js b/src/isomorphic/React.js index 4e03036be26e8..807a1c369f4f5 100644 --- a/src/isomorphic/React.js +++ b/src/isomorphic/React.js @@ -13,21 +13,22 @@ var ReactBaseClasses = require('ReactBaseClasses'); var ReactChildren = require('ReactChildren'); -var ReactClass = require('ReactClass'); var ReactDOMFactories = require('ReactDOMFactories'); var ReactElement = require('ReactElement'); var ReactPropTypes = require('ReactPropTypes'); var ReactVersion = require('ReactVersion'); var onlyChild = require('onlyChild'); -var warning = require('fbjs/lib/warning'); var checkPropTypes = require('checkPropTypes'); +var createReactClass = require('createClass'); var createElement = ReactElement.createElement; var createFactory = ReactElement.createFactory; var cloneElement = ReactElement.cloneElement; if (__DEV__) { + var warning = require('fbjs/lib/warning'); + var canDefineProperty = require('canDefineProperty'); var ReactElementValidator = require('ReactElementValidator'); createElement = ReactElementValidator.createElement; createFactory = ReactElementValidator.createFactory; @@ -38,20 +39,6 @@ var createMixin = function(mixin) { return mixin; }; -if (__DEV__) { - var warnedForCreateMixin = false; - - createMixin = function(mixin) { - warning( - warnedForCreateMixin, - 'React.createMixin is deprecated and should not be used. You ' + - 'can use this mixin directly instead.', - ); - warnedForCreateMixin = true; - return mixin; - }; -} - var React = { // Modern @@ -70,12 +57,13 @@ var React = { cloneElement: cloneElement, isValidElement: ReactElement.isValidElement, + // TODO (bvaughn) Remove these getters in 16.0.0-alpha.10 + PropTypes: ReactPropTypes, checkPropTypes: checkPropTypes, + createClass: createReactClass, // Classic - PropTypes: ReactPropTypes, - createClass: ReactClass.createClass, createFactory: createFactory, createMixin: createMixin, @@ -84,6 +72,80 @@ var React = { DOM: ReactDOMFactories, version: ReactVersion, + + __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: { + ReactCurrentOwner: require('ReactCurrentOwner'), + }, }; +if (__DEV__) { + Object.assign(React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED, { + // These should not be included in production. + ReactComponentTreeHook: require('ReactComponentTreeHook'), + ReactDebugCurrentFrame: require('ReactDebugCurrentFrame'), + }); + + let warnedForCheckPropTypes = false; + let warnedForCreateMixin = false; + let warnedForCreateClass = false; + let warnedForPropTypes = false; + + React.createMixin = function(mixin) { + warning( + warnedForCreateMixin, + 'React.createMixin is deprecated and should not be used. You ' + + 'can use this mixin directly instead.', + ); + warnedForCreateMixin = true; + return mixin; + }; + + // TODO (bvaughn) Remove both of these deprecation warnings in 16.0.0-alpha.10 + if (canDefineProperty) { + Object.defineProperty(React, 'checkPropTypes', { + get() { + warning( + warnedForCheckPropTypes, + 'checkPropTypes has been moved to a separate package. ' + + 'Accessing React.checkPropTypes is no longer supported ' + + 'and will be removed completely in React 16. ' + + 'Use the prop-types package on npm instead. ' + + '(https://fb.me/migrating-from-react-proptypes)', + ); + warnedForCheckPropTypes = true; + return ReactPropTypes; + }, + }); + + Object.defineProperty(React, 'createClass', { + get: function() { + warning( + warnedForCreateClass, + 'React.createClass is no longer supported. Use a plain JavaScript ' + + "class instead. If you're not yet ready to migrate, " + + 'create-react-class is available on npm as a drop-in replacement. ' + + '(https://fb.me/migrating-from-react-create-class)', + ); + warnedForCreateClass = true; + return createReactClass; + }, + }); + + Object.defineProperty(React, 'PropTypes', { + get() { + warning( + warnedForPropTypes, + 'PropTypes has been moved to a separate package. ' + + 'Accessing React.PropTypes is no longer supported ' + + 'and will be removed completely in React 16. ' + + 'Use the prop-types package on npm instead. ' + + '(https://fb.me/migrating-from-react-proptypes)', + ); + warnedForPropTypes = true; + return ReactPropTypes; + }, + }); + } +} + module.exports = React; diff --git a/src/isomorphic/__tests__/React-test.js b/src/isomorphic/__tests__/React-test.js index ec007f16a32b5..bd5d5bc1aaa4b 100644 --- a/src/isomorphic/__tests__/React-test.js +++ b/src/isomorphic/__tests__/React-test.js @@ -27,4 +27,48 @@ describe('React', () => { 'React.createMixin is deprecated and should not be used', ); }); + + it('should warn once when attempting to access React.createClass', () => { + spyOn(console, 'error'); + let createClass = React.createClass; + createClass = React.createClass; + expect(createClass).not.toBe(undefined); + expectDev(console.error.calls.count()).toBe(1); + expectDev(console.error.calls.argsFor(0)[0]).toContain( + 'React.createClass is no longer supported. Use a plain JavaScript ' + + "class instead. If you're not yet ready to migrate, " + + 'create-react-class is available on npm as a drop-in replacement. ' + + '(https://fb.me/migrating-from-react-create-class)', + ); + }); + + it('should warn once when attempting to access React.PropTypes', () => { + spyOn(console, 'error'); + let PropTypes = React.PropTypes; + PropTypes = React.PropTypes; + expect(PropTypes).not.toBe(undefined); + expectDev(console.error.calls.count()).toBe(1); + expectDev(console.error.calls.argsFor(0)[0]).toContain( + 'PropTypes has been moved to a separate package. ' + + 'Accessing React.PropTypes is no longer supported ' + + 'and will be removed completely in React 16. ' + + 'Use the prop-types package on npm instead. ' + + '(https://fb.me/migrating-from-react-proptypes)', + ); + }); + + it('should warn once when attempting to access React.checkPropTypes', () => { + spyOn(console, 'error'); + let checkPropTypes = React.checkPropTypes; + checkPropTypes = React.checkPropTypes; + expect(checkPropTypes).not.toBe(undefined); + expectDev(console.error.calls.count()).toBe(1); + expectDev(console.error.calls.argsFor(0)[0]).toContain( + 'checkPropTypes has been moved to a separate package. ' + + 'Accessing React.checkPropTypes is no longer supported ' + + 'and will be removed completely in React 16. ' + + 'Use the prop-types package on npm instead. ' + + '(https://fb.me/migrating-from-react-proptypes)', + ); + }); }); diff --git a/src/shared/utils/flattenChildren.js b/src/isomorphic/children/flattenChildren.js similarity index 94% rename from src/shared/utils/flattenChildren.js rename to src/isomorphic/children/flattenChildren.js index 21a5da7140abb..c6caa802bea29 100644 --- a/src/shared/utils/flattenChildren.js +++ b/src/isomorphic/children/flattenChildren.js @@ -28,7 +28,7 @@ if ( // https://github.com/facebook/react/issues/7240 // Remove the inline requires when we don't need them anymore: // https://github.com/facebook/react/pull/7178 - ReactComponentTreeHook = require('react/lib/ReactComponentTreeHook'); + ReactComponentTreeHook = require('ReactComponentTreeHook'); } /** @@ -49,7 +49,7 @@ function flattenSingleChildIntoContext( const keyUnique = result[name] === undefined; if (__DEV__) { if (!ReactComponentTreeHook) { - ReactComponentTreeHook = require('react/lib/ReactComponentTreeHook'); + ReactComponentTreeHook = require('ReactComponentTreeHook'); } if (!keyUnique) { warning( diff --git a/src/shared/utils/traverseAllChildren.js b/src/isomorphic/children/traverseAllChildren.js similarity index 98% rename from src/shared/utils/traverseAllChildren.js rename to src/isomorphic/children/traverseAllChildren.js index dd8b49fb58006..a68d1816d68ab 100644 --- a/src/shared/utils/traverseAllChildren.js +++ b/src/isomorphic/children/traverseAllChildren.js @@ -11,8 +11,8 @@ 'use strict'; -var ReactCurrentOwner = require('react/lib/ReactCurrentOwner'); var REACT_ELEMENT_TYPE = require('ReactElementSymbol'); +var ReactCurrentOwner = require('ReactCurrentOwner'); var getIteratorFn = require('getIteratorFn'); var invariant = require('fbjs/lib/invariant'); diff --git a/src/isomorphic/classic/__tests__/ReactContextValidator-test.js b/src/isomorphic/classic/__tests__/ReactContextValidator-test.js index dfd0f21809005..0630f588cc7a2 100644 --- a/src/isomorphic/classic/__tests__/ReactContextValidator-test.js +++ b/src/isomorphic/classic/__tests__/ReactContextValidator-test.js @@ -17,6 +17,7 @@ 'use strict'; +var PropTypes; var React; var ReactDOM; var ReactTestUtils; @@ -29,6 +30,7 @@ describe('ReactContextValidator', () => { beforeEach(() => { jest.resetModules(); + PropTypes = require('prop-types'); React = require('react'); ReactDOM = require('react-dom'); ReactTestUtils = require('ReactTestUtils'); @@ -38,33 +40,31 @@ describe('ReactContextValidator', () => { // ensure that this is not required for ES6 classes with Flow. it('should filter out context not in contextTypes', () => { - var Component = React.createClass({ - contextTypes: { - foo: React.PropTypes.string, - }, - - render: function() { + class Component extends React.Component { + render() { return
    ; - }, - }); - - var ComponentInFooBarContext = React.createClass({ - childContextTypes: { - foo: React.PropTypes.string, - bar: React.PropTypes.number, - }, + } + } + Component.contextTypes = { + foo: PropTypes.string, + }; - getChildContext: function() { + class ComponentInFooBarContext extends React.Component { + getChildContext() { return { foo: 'abc', bar: 123, }; - }, + } - render: function() { + render() { return ; - }, - }); + } + } + ComponentInFooBarContext.childContextTypes = { + foo: PropTypes.string, + bar: PropTypes.number, + }; var instance = ReactTestUtils.renderIntoDocument( , @@ -77,47 +77,45 @@ describe('ReactContextValidator', () => { var actualShouldComponentUpdate; var actualComponentWillUpdate; - var Parent = React.createClass({ - childContextTypes: { - foo: React.PropTypes.string.isRequired, - bar: React.PropTypes.string.isRequired, - }, - - getChildContext: function() { + class Parent extends React.Component { + getChildContext() { return { foo: this.props.foo, bar: 'bar', }; - }, + } - render: function() { + render() { return ; - }, - }); - - var Component = React.createClass({ - contextTypes: { - foo: React.PropTypes.string, - }, + } + } + Parent.childContextTypes = { + foo: PropTypes.string.isRequired, + bar: PropTypes.string.isRequired, + }; - componentWillReceiveProps: function(nextProps, nextContext) { + class Component extends React.Component { + componentWillReceiveProps(nextProps, nextContext) { actualComponentWillReceiveProps = nextContext; return true; - }, + } - shouldComponentUpdate: function(nextProps, nextState, nextContext) { + shouldComponentUpdate(nextProps, nextState, nextContext) { actualShouldComponentUpdate = nextContext; return true; - }, + } - componentWillUpdate: function(nextProps, nextState, nextContext) { + componentWillUpdate(nextProps, nextState, nextContext) { actualComponentWillUpdate = nextContext; - }, + } - render: function() { + render() { return
    ; - }, - }); + } + } + Component.contextTypes = { + foo: PropTypes.string, + }; var container = document.createElement('div'); ReactDOM.render(, container); @@ -130,37 +128,35 @@ describe('ReactContextValidator', () => { it('should pass previous context to lifecycles', () => { var actualComponentDidUpdate; - var Parent = React.createClass({ - childContextTypes: { - foo: React.PropTypes.string.isRequired, - bar: React.PropTypes.string.isRequired, - }, - - getChildContext: function() { + class Parent extends React.Component { + getChildContext() { return { foo: this.props.foo, bar: 'bar', }; - }, + } - render: function() { + render() { return ; - }, - }); - - var Component = React.createClass({ - contextTypes: { - foo: React.PropTypes.string, - }, + } + } + Parent.childContextTypes = { + foo: PropTypes.string.isRequired, + bar: PropTypes.string.isRequired, + }; - componentDidUpdate: function(prevProps, prevState, prevContext) { + class Component extends React.Component { + componentDidUpdate(prevProps, prevState, prevContext) { actualComponentDidUpdate = prevContext; - }, + } - render: function() { + render() { return
    ; - }, - }); + } + } + Component.contextTypes = { + foo: PropTypes.string, + }; var container = document.createElement('div'); ReactDOM.render(, container); @@ -171,15 +167,14 @@ describe('ReactContextValidator', () => { it('should check context types', () => { spyOn(console, 'error'); - var Component = React.createClass({ - contextTypes: { - foo: React.PropTypes.string.isRequired, - }, - - render: function() { + class Component extends React.Component { + render() { return
    ; - }, - }); + } + } + Component.contextTypes = { + foo: PropTypes.string.isRequired, + }; ReactTestUtils.renderIntoDocument(); @@ -191,21 +186,20 @@ describe('ReactContextValidator', () => { ' in Component (at **)', ); - var ComponentInFooStringContext = React.createClass({ - childContextTypes: { - foo: React.PropTypes.string, - }, - - getChildContext: function() { + class ComponentInFooStringContext extends React.Component { + getChildContext() { return { foo: this.props.fooValue, }; - }, + } - render: function() { + render() { return ; - }, - }); + } + } + ComponentInFooStringContext.childContextTypes = { + foo: PropTypes.string, + }; ReactTestUtils.renderIntoDocument( , @@ -214,21 +208,20 @@ describe('ReactContextValidator', () => { // Previous call should not error expectDev(console.error.calls.count()).toBe(1); - var ComponentInFooNumberContext = React.createClass({ - childContextTypes: { - foo: React.PropTypes.number, - }, - - getChildContext: function() { + class ComponentInFooNumberContext extends React.Component { + getChildContext() { return { foo: this.props.fooValue, }; - }, + } - render: function() { + render() { return ; - }, - }); + } + } + ComponentInFooNumberContext.childContextTypes = { + foo: PropTypes.number, + }; ReactTestUtils.renderIntoDocument( , @@ -247,20 +240,19 @@ describe('ReactContextValidator', () => { it('should check child context types', () => { spyOn(console, 'error'); - var Component = React.createClass({ - childContextTypes: { - foo: React.PropTypes.string.isRequired, - bar: React.PropTypes.number, - }, - - getChildContext: function() { + class Component extends React.Component { + getChildContext() { return this.props.testContext; - }, + } - render: function() { + render() { return
    ; - }, - }); + } + } + Component.childContextTypes = { + foo: PropTypes.string.isRequired, + bar: PropTypes.number, + }; ReactTestUtils.renderIntoDocument(); expectDev(console.error.calls.count()).toBe(1); @@ -298,7 +290,7 @@ describe('ReactContextValidator', () => { class ComponentA extends React.Component { static childContextTypes = { - foo: React.PropTypes.string.isRequired, + foo: PropTypes.string.isRequired, }; render() { return
    ; @@ -306,7 +298,7 @@ describe('ReactContextValidator', () => { } class ComponentB extends React.Component { static childContextTypes = { - foo: React.PropTypes.string.isRequired, + foo: PropTypes.string.isRequired, }; render() { return
    ; @@ -340,7 +332,7 @@ describe('ReactContextValidator', () => { class ParentContextProvider extends React.Component { static childContextTypes = { - foo: React.PropTypes.number, + foo: PropTypes.number, }; getChildContext() { return { @@ -354,7 +346,7 @@ describe('ReactContextValidator', () => { class MiddleMissingContext extends React.Component { static childContextTypes = { - bar: React.PropTypes.string.isRequired, + bar: PropTypes.string.isRequired, }; render() { return ; @@ -362,16 +354,16 @@ describe('ReactContextValidator', () => { } var childContext; - var ChildContextConsumer = React.createClass({ - contextTypes: { - bar: React.PropTypes.string.isRequired, - foo: React.PropTypes.string.isRequired, - }, - render: function() { + class ChildContextConsumer extends React.Component { + render() { childContext = this.context; return
    ; - }, - }); + } + } + ChildContextConsumer.contextTypes = { + bar: PropTypes.string.isRequired, + foo: PropTypes.string.isRequired, + }; ReactTestUtils.renderIntoDocument(); expect(childContext.bar).toBeUndefined(); diff --git a/src/isomorphic/classic/class/ReactClass.js b/src/isomorphic/classic/class/ReactClass.js deleted file mode 100644 index fb1c07ec08b75..0000000000000 --- a/src/isomorphic/classic/class/ReactClass.js +++ /dev/null @@ -1,833 +0,0 @@ -/** - * Copyright 2013-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @providesModule ReactClass - */ - -'use strict'; - -var ReactBaseClasses = require('ReactBaseClasses'); -var ReactElement = require('ReactElement'); -var ReactNoopUpdateQueue = require('ReactNoopUpdateQueue'); - -var emptyObject = require('fbjs/lib/emptyObject'); -var invariant = require('fbjs/lib/invariant'); -var warning = require('fbjs/lib/warning'); - -var ReactComponent = ReactBaseClasses.Component; - -var MIXINS_KEY = 'mixins'; - -// Helper function to allow the creation of anonymous functions which do not -// have .name set to the name of the variable being assigned to. -function identity(fn) { - return fn; -} - -/** - * Policies that describe methods in `ReactClassInterface`. - */ -type SpecPolicy = - /** - * These methods may be defined only once by the class specification or mixin. - */ - | 'DEFINE_ONCE' /** - * These methods may be defined by both the class specification and mixins. - * Subsequent definitions will be chained. These methods must return void. - */ - | 'DEFINE_MANY' /** - * These methods are overriding the base class. - */ - | 'OVERRIDE_BASE' /** - * These methods are similar to DEFINE_MANY, except we assume they return - * objects. We try to merge the keys of the return values of all the mixed in - * functions. If there is a key conflict we throw. - */ - | 'DEFINE_MANY_MERGED'; - -/** - * Composite components are higher-level components that compose other composite - * or host components. - * - * To create a new type of `ReactClass`, pass a specification of - * your new class to `React.createClass`. The only requirement of your class - * specification is that you implement a `render` method. - * - * var MyComponent = React.createClass({ - * render: function() { - * return
    Hello World
    ; - * } - * }); - * - * The class specification supports a specific protocol of methods that have - * special meaning (e.g. `render`). See `ReactClassInterface` for - * more the comprehensive protocol. Any other properties and methods in the - * class specification will be available on the prototype. - * - * @interface ReactClassInterface - * @internal - */ -var ReactClassInterface: {[key: string]: SpecPolicy} = { - /** - * An array of Mixin objects to include when defining your component. - * - * @type {array} - * @optional - */ - mixins: 'DEFINE_MANY', - - /** - * An object containing properties and methods that should be defined on - * the component's constructor instead of its prototype (static methods). - * - * @type {object} - * @optional - */ - statics: 'DEFINE_MANY', - - /** - * Definition of prop types for this component. - * - * @type {object} - * @optional - */ - propTypes: 'DEFINE_MANY', - - /** - * Definition of context types for this component. - * - * @type {object} - * @optional - */ - contextTypes: 'DEFINE_MANY', - - /** - * Definition of context types this component sets for its children. - * - * @type {object} - * @optional - */ - childContextTypes: 'DEFINE_MANY', - - // ==== Definition methods ==== - - /** - * Invoked when the component is mounted. Values in the mapping will be set on - * `this.props` if that prop is not specified (i.e. using an `in` check). - * - * This method is invoked before `getInitialState` and therefore cannot rely - * on `this.state` or use `this.setState`. - * - * @return {object} - * @optional - */ - getDefaultProps: 'DEFINE_MANY_MERGED', - - /** - * Invoked once before the component is mounted. The return value will be used - * as the initial value of `this.state`. - * - * getInitialState: function() { - * return { - * isOn: false, - * fooBaz: new BazFoo() - * } - * } - * - * @return {object} - * @optional - */ - getInitialState: 'DEFINE_MANY_MERGED', - - /** - * @return {object} - * @optional - */ - getChildContext: 'DEFINE_MANY_MERGED', - - /** - * Uses props from `this.props` and state from `this.state` to render the - * structure of the component. - * - * No guarantees are made about when or how often this method is invoked, so - * it must not have side effects. - * - * render: function() { - * var name = this.props.name; - * return
    Hello, {name}!
    ; - * } - * - * @return {ReactComponent} - * @required - */ - render: 'DEFINE_ONCE', - - // ==== Delegate methods ==== - - /** - * Invoked when the component is initially created and about to be mounted. - * This may have side effects, but any external subscriptions or data created - * by this method must be cleaned up in `componentWillUnmount`. - * - * @optional - */ - componentWillMount: 'DEFINE_MANY', - - /** - * Invoked when the component has been mounted and has a DOM representation. - * However, there is no guarantee that the DOM node is in the document. - * - * Use this as an opportunity to operate on the DOM when the component has - * been mounted (initialized and rendered) for the first time. - * - * @param {DOMElement} rootNode DOM element representing the component. - * @optional - */ - componentDidMount: 'DEFINE_MANY', - - /** - * Invoked before the component receives new props. - * - * Use this as an opportunity to react to a prop transition by updating the - * state using `this.setState`. Current props are accessed via `this.props`. - * - * componentWillReceiveProps: function(nextProps, nextContext) { - * this.setState({ - * likesIncreasing: nextProps.likeCount > this.props.likeCount - * }); - * } - * - * NOTE: There is no equivalent `componentWillReceiveState`. An incoming prop - * transition may cause a state change, but the opposite is not true. If you - * need it, you are probably looking for `componentWillUpdate`. - * - * @param {object} nextProps - * @optional - */ - componentWillReceiveProps: 'DEFINE_MANY', - - /** - * Invoked while deciding if the component should be updated as a result of - * receiving new props, state and/or context. - * - * Use this as an opportunity to `return false` when you're certain that the - * transition to the new props/state/context will not require a component - * update. - * - * shouldComponentUpdate: function(nextProps, nextState, nextContext) { - * return !equal(nextProps, this.props) || - * !equal(nextState, this.state) || - * !equal(nextContext, this.context); - * } - * - * @param {object} nextProps - * @param {?object} nextState - * @param {?object} nextContext - * @return {boolean} True if the component should update. - * @optional - */ - shouldComponentUpdate: 'DEFINE_ONCE', - - /** - * Invoked when the component is about to update due to a transition from - * `this.props`, `this.state` and `this.context` to `nextProps`, `nextState` - * and `nextContext`. - * - * Use this as an opportunity to perform preparation before an update occurs. - * - * NOTE: You **cannot** use `this.setState()` in this method. - * - * @param {object} nextProps - * @param {?object} nextState - * @param {?object} nextContext - * @param {ReactReconcileTransaction} transaction - * @optional - */ - componentWillUpdate: 'DEFINE_MANY', - - /** - * Invoked when the component's DOM representation has been updated. - * - * Use this as an opportunity to operate on the DOM when the component has - * been updated. - * - * @param {object} prevProps - * @param {?object} prevState - * @param {?object} prevContext - * @param {DOMElement} rootNode DOM element representing the component. - * @optional - */ - componentDidUpdate: 'DEFINE_MANY', - - /** - * Invoked when the component is about to be removed from its parent and have - * its DOM representation destroyed. - * - * Use this as an opportunity to deallocate any external resources. - * - * NOTE: There is no `componentDidUnmount` since your component will have been - * destroyed by that point. - * - * @optional - */ - componentWillUnmount: 'DEFINE_MANY', - - // ==== Advanced methods ==== - - /** - * Updates the component's currently mounted DOM representation. - * - * By default, this implements React's rendering and reconciliation algorithm. - * Sophisticated clients may wish to override this. - * - * @param {ReactReconcileTransaction} transaction - * @internal - * @overridable - */ - updateComponent: 'OVERRIDE_BASE', -}; - -/** - * Mapping from class specification keys to special processing functions. - * - * Although these are declared like instance properties in the specification - * when defining classes using `React.createClass`, they are actually static - * and are accessible on the constructor instead of the prototype. Despite - * being static, they must be defined outside of the "statics" key under - * which all other static methods are defined. - */ -var RESERVED_SPEC_KEYS = { - displayName: function(Constructor, displayName) { - Constructor.displayName = displayName; - }, - mixins: function(Constructor, mixins) { - if (mixins) { - for (var i = 0; i < mixins.length; i++) { - mixSpecIntoComponent(Constructor, mixins[i]); - } - } - }, - childContextTypes: function(Constructor, childContextTypes) { - if (__DEV__) { - validateTypeDef(Constructor, childContextTypes, 'child context'); - } - Constructor.childContextTypes = Object.assign( - {}, - Constructor.childContextTypes, - childContextTypes, - ); - }, - contextTypes: function(Constructor, contextTypes) { - if (__DEV__) { - validateTypeDef(Constructor, contextTypes, 'context'); - } - Constructor.contextTypes = Object.assign( - {}, - Constructor.contextTypes, - contextTypes, - ); - }, - /** - * Special case getDefaultProps which should move into statics but requires - * automatic merging. - */ - getDefaultProps: function(Constructor, getDefaultProps) { - if (Constructor.getDefaultProps) { - Constructor.getDefaultProps = createMergedResultFunction( - Constructor.getDefaultProps, - getDefaultProps, - ); - } else { - Constructor.getDefaultProps = getDefaultProps; - } - }, - propTypes: function(Constructor, propTypes) { - if (__DEV__) { - validateTypeDef(Constructor, propTypes, 'prop'); - } - Constructor.propTypes = Object.assign({}, Constructor.propTypes, propTypes); - }, - statics: function(Constructor, statics) { - mixStaticSpecIntoComponent(Constructor, statics); - }, - autobind: function() {}, // noop -}; - -function validateTypeDef(Constructor, typeDef, location: string) { - for (var propName in typeDef) { - if (typeDef.hasOwnProperty(propName)) { - // use a warning instead of an invariant so components - // don't show up in prod but only in __DEV__ - warning( - typeof typeDef[propName] === 'function', - '%s: %s type `%s` is invalid; it must be a function, usually from ' + - 'React.PropTypes.', - Constructor.displayName || 'ReactClass', - location, - propName, - ); - } - } -} - -function validateMethodOverride(isAlreadyDefined, name) { - var specPolicy = ReactClassInterface.hasOwnProperty(name) - ? ReactClassInterface[name] - : null; - - // Disallow overriding of base class methods unless explicitly allowed. - if (ReactClassMixin.hasOwnProperty(name)) { - invariant( - specPolicy === 'OVERRIDE_BASE', - 'ReactClassInterface: You are attempting to override ' + - '`%s` from your class specification. Ensure that your method names ' + - 'do not overlap with React methods.', - name, - ); - } - - // Disallow defining methods more than once unless explicitly allowed. - if (isAlreadyDefined) { - invariant( - specPolicy === 'DEFINE_MANY' || specPolicy === 'DEFINE_MANY_MERGED', - 'ReactClassInterface: You are attempting to define ' + - '`%s` on your component more than once. This conflict may be due ' + - 'to a mixin.', - name, - ); - } -} - -/** - * Mixin helper which handles policy validation and reserved - * specification keys when building React classes. - */ -function mixSpecIntoComponent(Constructor, spec) { - if (!spec) { - if (__DEV__) { - var typeofSpec = typeof spec; - var isMixinValid = typeofSpec === 'object' && spec !== null; - - warning( - isMixinValid, - "%s: You're attempting to include a mixin that is either null " + - 'or not an object. Check the mixins included by the component, ' + - 'as well as any mixins they include themselves. ' + - 'Expected object but got %s.', - Constructor.displayName || 'ReactClass', - spec === null ? null : typeofSpec, - ); - } - - return; - } - - invariant( - typeof spec !== 'function', - "ReactClass: You're attempting to " + - 'use a component class or function as a mixin. Instead, just use a ' + - 'regular object.', - ); - invariant( - !ReactElement.isValidElement(spec), - "ReactClass: You're attempting to " + - 'use a component as a mixin. Instead, just use a regular object.', - ); - - var proto = Constructor.prototype; - var autoBindPairs = proto.__reactAutoBindPairs; - - // By handling mixins before any other properties, we ensure the same - // chaining order is applied to methods with DEFINE_MANY policy, whether - // mixins are listed before or after these methods in the spec. - if (spec.hasOwnProperty(MIXINS_KEY)) { - RESERVED_SPEC_KEYS.mixins(Constructor, spec.mixins); - } - - for (var name in spec) { - if (!spec.hasOwnProperty(name)) { - continue; - } - - if (name === MIXINS_KEY) { - // We have already handled mixins in a special case above. - continue; - } - - var property = spec[name]; - var isAlreadyDefined = proto.hasOwnProperty(name); - validateMethodOverride(isAlreadyDefined, name); - - if (RESERVED_SPEC_KEYS.hasOwnProperty(name)) { - RESERVED_SPEC_KEYS[name](Constructor, property); - } else { - // Setup methods on prototype: - // The following member methods should not be automatically bound: - // 1. Expected ReactClass methods (in the "interface"). - // 2. Overridden methods (that were mixed in). - var isReactClassMethod = ReactClassInterface.hasOwnProperty(name); - var isFunction = typeof property === 'function'; - var shouldAutoBind = isFunction && - !isReactClassMethod && - !isAlreadyDefined && - spec.autobind !== false; - - if (shouldAutoBind) { - autoBindPairs.push(name, property); - proto[name] = property; - } else { - if (isAlreadyDefined) { - var specPolicy = ReactClassInterface[name]; - - // These cases should already be caught by validateMethodOverride. - invariant( - isReactClassMethod && - (specPolicy === 'DEFINE_MANY_MERGED' || - specPolicy === 'DEFINE_MANY'), - 'ReactClass: Unexpected spec policy %s for key %s ' + - 'when mixing in component specs.', - specPolicy, - name, - ); - - // For methods which are defined more than once, call the existing - // methods before calling the new property, merging if appropriate. - if (specPolicy === 'DEFINE_MANY_MERGED') { - proto[name] = createMergedResultFunction(proto[name], property); - } else if (specPolicy === 'DEFINE_MANY') { - proto[name] = createChainedFunction(proto[name], property); - } - } else { - proto[name] = property; - if (__DEV__) { - // Add verbose displayName to the function, which helps when looking - // at profiling tools. - if (typeof property === 'function' && spec.displayName) { - proto[name].displayName = spec.displayName + '_' + name; - } - } - } - } - } - } -} - -function mixStaticSpecIntoComponent(Constructor, statics) { - if (!statics) { - return; - } - for (var name in statics) { - var property = statics[name]; - if (!statics.hasOwnProperty(name)) { - continue; - } - - var isReserved = name in RESERVED_SPEC_KEYS; - invariant( - !isReserved, - 'ReactClass: You are attempting to define a reserved ' + - 'property, `%s`, that shouldn\'t be on the "statics" key. Define it ' + - 'as an instance property instead; it will still be accessible on the ' + - 'constructor.', - name, - ); - - var isInherited = name in Constructor; - invariant( - !isInherited, - 'ReactClass: You are attempting to define ' + - '`%s` on your component more than once. This conflict may be ' + - 'due to a mixin.', - name, - ); - Constructor[name] = property; - } -} - -/** - * Merge two objects, but throw if both contain the same key. - * - * @param {object} one The first object, which is mutated. - * @param {object} two The second object - * @return {object} one after it has been mutated to contain everything in two. - */ -function mergeIntoWithNoDuplicateKeys(one, two) { - invariant( - one && two && typeof one === 'object' && typeof two === 'object', - 'mergeIntoWithNoDuplicateKeys(): Cannot merge non-objects.', - ); - - for (var key in two) { - if (two.hasOwnProperty(key)) { - invariant( - one[key] === undefined, - 'mergeIntoWithNoDuplicateKeys(): ' + - 'Tried to merge two objects with the same key: `%s`. This conflict ' + - 'may be due to a mixin; in particular, this may be caused by two ' + - 'getInitialState() or getDefaultProps() methods returning objects ' + - 'with clashing keys.', - key, - ); - one[key] = two[key]; - } - } - return one; -} - -/** - * Creates a function that invokes two functions and merges their return values. - * - * @param {function} one Function to invoke first. - * @param {function} two Function to invoke second. - * @return {function} Function that invokes the two argument functions. - * @private - */ -function createMergedResultFunction(one, two) { - return function mergedResult() { - var a = one.apply(this, arguments); - var b = two.apply(this, arguments); - if (a == null) { - return b; - } else if (b == null) { - return a; - } - var c = {}; - mergeIntoWithNoDuplicateKeys(c, a); - mergeIntoWithNoDuplicateKeys(c, b); - return c; - }; -} - -/** - * Creates a function that invokes two functions and ignores their return vales. - * - * @param {function} one Function to invoke first. - * @param {function} two Function to invoke second. - * @return {function} Function that invokes the two argument functions. - * @private - */ -function createChainedFunction(one, two) { - return function chainedFunction() { - one.apply(this, arguments); - two.apply(this, arguments); - }; -} - -/** - * Binds a method to the component. - * - * @param {object} component Component whose method is going to be bound. - * @param {function} method Method to be bound. - * @return {function} The bound method. - */ -function bindAutoBindMethod(component, method) { - var boundMethod = method.bind(component); - if (__DEV__) { - boundMethod.__reactBoundContext = component; - boundMethod.__reactBoundMethod = method; - boundMethod.__reactBoundArguments = null; - var componentName = component.constructor.displayName; - var _bind = boundMethod.bind; - boundMethod.bind = function(newThis, ...args) { - // User is trying to bind() an autobound method; we effectively will - // ignore the value of "this" that the user is trying to use, so - // let's warn. - if (newThis !== component && newThis !== null) { - warning( - false, - 'bind(): React component methods may only be bound to the ' + - 'component instance.\n\nSee %s', - componentName, - ); - } else if (!args.length) { - warning( - false, - 'bind(): You are binding a component method to the component. ' + - 'React does this for you automatically in a high-performance ' + - 'way, so you can safely remove this call.\n\nSee %s', - componentName, - ); - return boundMethod; - } - var reboundMethod = _bind.apply(boundMethod, arguments); - reboundMethod.__reactBoundContext = component; - reboundMethod.__reactBoundMethod = method; - reboundMethod.__reactBoundArguments = args; - return reboundMethod; - }; - } - return boundMethod; -} - -/** - * Binds all auto-bound methods in a component. - * - * @param {object} component Component whose method is going to be bound. - */ -function bindAutoBindMethods(component) { - var pairs = component.__reactAutoBindPairs; - for (var i = 0; i < pairs.length; i += 2) { - var autoBindKey = pairs[i]; - var method = pairs[i + 1]; - component[autoBindKey] = bindAutoBindMethod(component, method); - } -} - -/** - * Add more to the ReactClass base class. These are all legacy features and - * therefore not already part of the modern ReactComponent. - */ -var ReactClassMixin = { - /** - * TODO: This will be deprecated because state should always keep a consistent - * type signature and the only use case for this, is to avoid that. - */ - replaceState: function(newState, callback) { - this.updater.enqueueReplaceState(this, newState, callback, 'replaceState'); - }, - - /** - * Checks whether or not this composite component is mounted. - * @return {boolean} True if mounted, false otherwise. - * @protected - * @final - */ - isMounted: function() { - return this.updater.isMounted(this); - }, -}; - -var ReactClassComponent = function() {}; -Object.assign( - ReactClassComponent.prototype, - ReactComponent.prototype, - ReactClassMixin, -); - -/** - * Module for creating composite components. - * - * @class ReactClass - */ -var ReactClass = { - /** - * Creates a composite component class given a class specification. - * See https://facebook.github.io/react/docs/react-api.html#createclass - * - * @param {object} spec Class specification (which must define `render`). - * @return {function} Component constructor function. - * @public - */ - createClass: function(spec) { - // To keep our warnings more understandable, we'll use a little hack here to - // ensure that Constructor.name !== 'Constructor'. This makes sure we don't - // unnecessarily identify a class without displayName as 'Constructor'. - var Constructor = identity(function(props, context, updater) { - // This constructor gets overridden by mocks. The argument is used - // by mocks to assert on what gets mounted. - - if (__DEV__) { - warning( - this instanceof Constructor, - 'Something is calling a React component directly. Use a factory or ' + - 'JSX instead. See: https://fb.me/react-legacyfactory', - ); - } - - // Wire up auto-binding - if (this.__reactAutoBindPairs.length) { - bindAutoBindMethods(this); - } - - this.props = props; - this.context = context; - this.refs = emptyObject; - this.updater = updater || ReactNoopUpdateQueue; - - this.state = null; - - // ReactClasses doesn't have constructors. Instead, they use the - // getInitialState and componentWillMount methods for initialization. - - var initialState = this.getInitialState ? this.getInitialState() : null; - if (__DEV__) { - // We allow auto-mocks to proceed as if they're returning null. - if ( - initialState === undefined && this.getInitialState._isMockFunction - ) { - // This is probably bad practice. Consider warning here and - // deprecating this convenience. - initialState = null; - } - } - invariant( - typeof initialState === 'object' && !Array.isArray(initialState), - '%s.getInitialState(): must return an object or null', - Constructor.displayName || 'ReactCompositeComponent', - ); - - this.state = initialState; - }); - Constructor.prototype = new ReactClassComponent(); - Constructor.prototype.constructor = Constructor; - Constructor.prototype.__reactAutoBindPairs = []; - - mixSpecIntoComponent(Constructor, spec); - - // Initialize the defaultProps property after all mixins have been merged. - if (Constructor.getDefaultProps) { - Constructor.defaultProps = Constructor.getDefaultProps(); - } - - if (__DEV__) { - // This is a tag to indicate that the use of these method names is ok, - // since it's used with createClass. If it's not, then it's likely a - // mistake so we'll warn you to use the static property, property - // initializer or constructor respectively. - if (Constructor.getDefaultProps) { - Constructor.getDefaultProps.isReactClassApproved = {}; - } - if (Constructor.prototype.getInitialState) { - Constructor.prototype.getInitialState.isReactClassApproved = {}; - } - } - - invariant( - Constructor.prototype.render, - 'createClass(...): Class specification must implement a `render` method.', - ); - - if (__DEV__) { - warning( - !Constructor.prototype.componentShouldUpdate, - '%s has a method called ' + - 'componentShouldUpdate(). Did you mean shouldComponentUpdate()? ' + - 'The name is phrased as a question because the function is ' + - 'expected to return a value.', - spec.displayName || 'A component', - ); - warning( - !Constructor.prototype.componentWillRecieveProps, - '%s has a method called ' + - 'componentWillRecieveProps(). Did you mean componentWillReceiveProps()?', - spec.displayName || 'A component', - ); - } - - // Reduce time spent doing lookups by setting these on the prototype. - for (var methodName in ReactClassInterface) { - if (!Constructor.prototype[methodName]) { - Constructor.prototype[methodName] = null; - } - } - - return Constructor; - }, -}; - -module.exports = ReactClass; diff --git a/src/isomorphic/classic/class/__tests__/ReactBind-test.js b/src/isomorphic/classic/class/__tests__/ReactBind-test.js deleted file mode 100644 index 051db5a8fed29..0000000000000 --- a/src/isomorphic/classic/class/__tests__/ReactBind-test.js +++ /dev/null @@ -1,152 +0,0 @@ -/** - * Copyright 2013-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @emails react-core - */ -/*global global:true*/ -'use strict'; - -var React = require('react'); -var ReactTestUtils = require('ReactTestUtils'); - -// TODO: Test render and all stock methods. -describe('autobinding', () => { - it('Holds reference to instance', () => { - var mouseDidEnter = jest.fn(); - var mouseDidLeave = jest.fn(); - var mouseDidClick = jest.fn(); - - var TestBindComponent = React.createClass({ - getInitialState: function() { - return {something: 'hi'}; - }, - onMouseEnter: mouseDidEnter, - onMouseLeave: mouseDidLeave, - onClick: mouseDidClick, - - // auto binding only occurs on top level functions in class defs. - badIdeas: { - badBind: function() { - void this.state.something; - }, - }, - - render: function() { - return ( -
    - ); - }, - }); - - var instance1 = ReactTestUtils.renderIntoDocument(); - var rendered1 = instance1.refs.child; - - var instance2 = ReactTestUtils.renderIntoDocument(); - var rendered2 = instance2.refs.child; - - expect(function() { - var badIdea = instance1.badIdeas.badBind; - badIdea(); - }).toThrow(); - - expect(instance1.onClick).not.toBe(instance2.onClick); - - ReactTestUtils.Simulate.click(rendered1); - expect(mouseDidClick.mock.instances.length).toBe(1); - expect(mouseDidClick.mock.instances[0]).toBe(instance1); - - ReactTestUtils.Simulate.click(rendered2); - expect(mouseDidClick.mock.instances.length).toBe(2); - expect(mouseDidClick.mock.instances[1]).toBe(instance2); - - ReactTestUtils.Simulate.mouseOver(rendered1); - expect(mouseDidEnter.mock.instances.length).toBe(1); - expect(mouseDidEnter.mock.instances[0]).toBe(instance1); - - ReactTestUtils.Simulate.mouseOver(rendered2); - expect(mouseDidEnter.mock.instances.length).toBe(2); - expect(mouseDidEnter.mock.instances[1]).toBe(instance2); - - ReactTestUtils.Simulate.mouseOut(rendered1); - expect(mouseDidLeave.mock.instances.length).toBe(1); - expect(mouseDidLeave.mock.instances[0]).toBe(instance1); - - ReactTestUtils.Simulate.mouseOut(rendered2); - expect(mouseDidLeave.mock.instances.length).toBe(2); - expect(mouseDidLeave.mock.instances[1]).toBe(instance2); - }); - - it('works with mixins', () => { - var mouseDidClick = jest.fn(); - - var TestMixin = { - onClick: mouseDidClick, - }; - - var TestBindComponent = React.createClass({ - mixins: [TestMixin], - - render: function() { - return
    ; - }, - }); - - var instance1 = ReactTestUtils.renderIntoDocument(); - var rendered1 = instance1.refs.child; - - ReactTestUtils.Simulate.click(rendered1); - expect(mouseDidClick.mock.instances.length).toBe(1); - expect(mouseDidClick.mock.instances[0]).toBe(instance1); - }); - - it('warns if you try to bind to this', () => { - spyOn(console, 'error'); - - var TestBindComponent = React.createClass({ - handleClick: function() {}, - render: function() { - return
    ; - }, - }); - - ReactTestUtils.renderIntoDocument(); - - expectDev(console.error.calls.count()).toBe(1); - expectDev(console.error.calls.argsFor(0)[0]).toBe( - 'Warning: bind(): You are binding a component method to the component. ' + - 'React does this for you automatically in a high-performance ' + - 'way, so you can safely remove this call.\n\nSee TestBindComponent', - ); - }); - - it('does not warn if you pass an auto-bound method to setState', () => { - spyOn(console, 'error'); - - var TestBindComponent = React.createClass({ - getInitialState: function() { - return {foo: 1}; - }, - componentDidMount: function() { - this.setState({foo: 2}, this.handleUpdate); - }, - handleUpdate: function() {}, - render: function() { - return
    ; - }, - }); - - ReactTestUtils.renderIntoDocument(); - - expectDev(console.error.calls.count()).toBe(0); - }); -}); diff --git a/src/isomorphic/classic/class/__tests__/ReactBindOptout-test.js b/src/isomorphic/classic/class/__tests__/ReactBindOptout-test.js deleted file mode 100644 index 086329338fa4d..0000000000000 --- a/src/isomorphic/classic/class/__tests__/ReactBindOptout-test.js +++ /dev/null @@ -1,206 +0,0 @@ -/** - * Copyright 2013-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @emails react-core - */ -/*global global:true*/ -'use strict'; - -var React = require('react'); -var ReactTestUtils = require('ReactTestUtils'); - -// TODO: Test render and all stock methods. -describe('autobind optout', () => { - it('should work with manual binding', () => { - var mouseDidEnter = jest.fn(); - var mouseDidLeave = jest.fn(); - var mouseDidClick = jest.fn(); - - var TestBindComponent = React.createClass({ - autobind: false, - getInitialState: function() { - return {something: 'hi'}; - }, - onMouseEnter: mouseDidEnter, - onMouseLeave: mouseDidLeave, - onClick: mouseDidClick, - - render: function() { - return ( -
    - ); - }, - }); - - var instance1 = ReactTestUtils.renderIntoDocument(); - var rendered1 = instance1.refs.child; - - var instance2 = ReactTestUtils.renderIntoDocument(); - var rendered2 = instance2.refs.child; - - ReactTestUtils.Simulate.click(rendered1); - expect(mouseDidClick.mock.instances.length).toBe(1); - expect(mouseDidClick.mock.instances[0]).toBe(instance1); - - ReactTestUtils.Simulate.click(rendered2); - expect(mouseDidClick.mock.instances.length).toBe(2); - expect(mouseDidClick.mock.instances[1]).toBe(instance2); - - ReactTestUtils.Simulate.mouseOver(rendered1); - expect(mouseDidEnter.mock.instances.length).toBe(1); - expect(mouseDidEnter.mock.instances[0]).toBe(instance1); - - ReactTestUtils.Simulate.mouseOver(rendered2); - expect(mouseDidEnter.mock.instances.length).toBe(2); - expect(mouseDidEnter.mock.instances[1]).toBe(instance2); - - ReactTestUtils.Simulate.mouseOut(rendered1); - expect(mouseDidLeave.mock.instances.length).toBe(1); - expect(mouseDidLeave.mock.instances[0]).toBe(instance1); - - ReactTestUtils.Simulate.mouseOut(rendered2); - expect(mouseDidLeave.mock.instances.length).toBe(2); - expect(mouseDidLeave.mock.instances[1]).toBe(instance2); - }); - - it('should not hold reference to instance', () => { - var mouseDidClick = function() { - void this.state.something; - }; - - var TestBindComponent = React.createClass({ - autobind: false, - getInitialState: function() { - return {something: 'hi'}; - }, - onClick: mouseDidClick, - - // auto binding only occurs on top level functions in class defs. - badIdeas: { - badBind: function() { - void this.state.something; - }, - }, - - render: function() { - return
    ; - }, - }); - - var instance1 = ReactTestUtils.renderIntoDocument(); - var rendered1 = instance1.refs.child; - - var instance2 = ReactTestUtils.renderIntoDocument(); - var rendered2 = instance2.refs.child; - - expect(function() { - var badIdea = instance1.badIdeas.badBind; - badIdea(); - }).toThrow(); - - expect(instance1.onClick).toBe(instance2.onClick); - - expect(function() { - ReactTestUtils.Simulate.click(rendered1); - }).toThrow(); - - expect(function() { - ReactTestUtils.Simulate.click(rendered2); - }).toThrow(); - }); - - it('works with mixins that have not opted out of autobinding', () => { - var mouseDidClick = jest.fn(); - - var TestMixin = { - onClick: mouseDidClick, - }; - - var TestBindComponent = React.createClass({ - mixins: [TestMixin], - - render: function() { - return
    ; - }, - }); - - var instance1 = ReactTestUtils.renderIntoDocument(); - var rendered1 = instance1.refs.child; - - ReactTestUtils.Simulate.click(rendered1); - expect(mouseDidClick.mock.instances.length).toBe(1); - expect(mouseDidClick.mock.instances[0]).toBe(instance1); - }); - - it('works with mixins that have opted out of autobinding', () => { - var mouseDidClick = jest.fn(); - - var TestMixin = { - autobind: false, - onClick: mouseDidClick, - }; - - var TestBindComponent = React.createClass({ - mixins: [TestMixin], - - render: function() { - return
    ; - }, - }); - - var instance1 = ReactTestUtils.renderIntoDocument(); - var rendered1 = instance1.refs.child; - - ReactTestUtils.Simulate.click(rendered1); - expect(mouseDidClick.mock.instances.length).toBe(1); - expect(mouseDidClick.mock.instances[0]).toBe(instance1); - }); - - it('does not warn if you try to bind to this', () => { - spyOn(console, 'error'); - - var TestBindComponent = React.createClass({ - autobind: false, - handleClick: function() {}, - render: function() { - return
    ; - }, - }); - - ReactTestUtils.renderIntoDocument(); - - expectDev(console.error.calls.count()).toBe(0); - }); - - it('does not warn if you pass an manually bound method to setState', () => { - spyOn(console, 'error'); - - var TestBindComponent = React.createClass({ - autobind: false, - getInitialState: function() { - return {foo: 1}; - }, - componentDidMount: function() { - this.setState({foo: 2}, this.handleUpdate.bind(this)); - }, - handleUpdate: function() {}, - render: function() { - return
    ; - }, - }); - - ReactTestUtils.renderIntoDocument(); - - expectDev(console.error.calls.count()).toBe(0); - }); -}); diff --git a/src/isomorphic/classic/class/__tests__/ReactClassMixin-test.js b/src/isomorphic/classic/class/__tests__/ReactClassMixin-test.js deleted file mode 100644 index 2f079a69fc19d..0000000000000 --- a/src/isomorphic/classic/class/__tests__/ReactClassMixin-test.js +++ /dev/null @@ -1,532 +0,0 @@ -/** - * Copyright 2013-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @emails react-core - */ - -'use strict'; - -var React; -var ReactTestUtils; - -var TestComponent; -var TestComponentWithPropTypes; -var TestComponentWithReverseSpec; -var mixinPropValidator; -var componentPropValidator; - -describe('ReactClass-mixin', () => { - beforeEach(() => { - React = require('react'); - ReactTestUtils = require('ReactTestUtils'); - mixinPropValidator = jest.fn(); - componentPropValidator = jest.fn(); - - var MixinA = { - propTypes: { - propA: function() {}, - }, - componentDidMount: function() { - this.props.listener('MixinA didMount'); - }, - }; - - var MixinB = { - mixins: [MixinA], - propTypes: { - propB: function() {}, - }, - componentDidMount: function() { - this.props.listener('MixinB didMount'); - }, - }; - - var MixinBWithReverseSpec = { - componentDidMount: function() { - this.props.listener('MixinBWithReverseSpec didMount'); - }, - mixins: [MixinA], - }; - - var MixinC = { - statics: { - staticC: function() {}, - }, - componentDidMount: function() { - this.props.listener('MixinC didMount'); - }, - }; - - var MixinD = { - propTypes: { - value: mixinPropValidator, - }, - }; - - TestComponent = React.createClass({ - mixins: [MixinB, MixinC, MixinD], - statics: { - staticComponent: function() {}, - }, - propTypes: { - propComponent: function() {}, - }, - componentDidMount: function() { - this.props.listener('Component didMount'); - }, - render: function() { - return
    ; - }, - }); - - TestComponentWithReverseSpec = React.createClass({ - render: function() { - return
    ; - }, - componentDidMount: function() { - this.props.listener('Component didMount'); - }, - mixins: [MixinBWithReverseSpec, MixinC, MixinD], - }); - - TestComponentWithPropTypes = React.createClass({ - mixins: [MixinD], - propTypes: { - value: componentPropValidator, - }, - render: function() { - return
    ; - }, - }); - }); - - it('should support merging propTypes and statics', () => { - var listener = jest.fn(); - var instance = ; - instance = ReactTestUtils.renderIntoDocument(instance); - - var instancePropTypes = instance.constructor.propTypes; - - expect('propA' in instancePropTypes).toBe(true); - expect('propB' in instancePropTypes).toBe(true); - expect('propComponent' in instancePropTypes).toBe(true); - - expect('staticC' in TestComponent).toBe(true); - expect('staticComponent' in TestComponent).toBe(true); - }); - - it('should support chaining delegate functions', () => { - var listener = jest.fn(); - var instance = ; - ReactTestUtils.renderIntoDocument(instance); - - expect(listener.mock.calls).toEqual([ - ['MixinA didMount'], - ['MixinB didMount'], - ['MixinC didMount'], - ['Component didMount'], - ]); - }); - - it('should chain functions regardless of spec property order', () => { - var listener = jest.fn(); - var instance = ; - ReactTestUtils.renderIntoDocument(instance); - - expect(listener.mock.calls).toEqual([ - ['MixinA didMount'], - ['MixinBWithReverseSpec didMount'], - ['MixinC didMount'], - ['Component didMount'], - ]); - }); - - it('should validate prop types via mixins', () => { - expect(TestComponent.propTypes).toBeDefined(); - expect(TestComponent.propTypes.value).toBe(mixinPropValidator); - }); - - it('should override mixin prop types with class prop types', () => { - // Sanity check... - expect(componentPropValidator).not.toBe(mixinPropValidator); - // Actually check... - expect(TestComponentWithPropTypes.propTypes).toBeDefined(); - expect(TestComponentWithPropTypes.propTypes.value).not.toBe( - mixinPropValidator, - ); - expect(TestComponentWithPropTypes.propTypes.value).toBe( - componentPropValidator, - ); - }); - - it('should support mixins with getInitialState()', () => { - var Mixin = { - getInitialState: function() { - return {mixin: true}; - }, - }; - var Component = React.createClass({ - mixins: [Mixin], - getInitialState: function() { - return {component: true}; - }, - render: function() { - return ; - }, - }); - var instance = ReactTestUtils.renderIntoDocument(); - expect(instance.state.component).toBe(true); - expect(instance.state.mixin).toBe(true); - }); - - it('should throw with conflicting getInitialState() methods', () => { - var Mixin = { - getInitialState: function() { - return {x: true}; - }, - }; - var Component = React.createClass({ - mixins: [Mixin], - getInitialState: function() { - return {x: true}; - }, - render: function() { - return ; - }, - }); - expect(function() { - ReactTestUtils.renderIntoDocument(); - }).toThrowError( - 'mergeIntoWithNoDuplicateKeys(): Tried to merge two objects with the ' + - 'same key: `x`. This conflict may be due to a mixin; in particular, ' + - 'this may be caused by two getInitialState() or getDefaultProps() ' + - 'methods returning objects with clashing keys.', - ); - }); - - it('should not mutate objects returned by getInitialState()', () => { - var Mixin = { - getInitialState: function() { - return Object.freeze({mixin: true}); - }, - }; - var Component = React.createClass({ - mixins: [Mixin], - getInitialState: function() { - return Object.freeze({component: true}); - }, - render: function() { - return ; - }, - }); - expect(() => { - ReactTestUtils.renderIntoDocument(); - }).not.toThrow(); - }); - - it('should support statics in mixins', () => { - var Mixin = { - statics: { - foo: 'bar', - }, - }; - var Component = React.createClass({ - mixins: [Mixin], - - statics: { - abc: 'def', - }, - - render: function() { - return ; - }, - }); - var instance = ; - instance = ReactTestUtils.renderIntoDocument(instance); - expect(instance.constructor.foo).toBe('bar'); - expect(Component.foo).toBe('bar'); - expect(instance.constructor.abc).toBe('def'); - expect(Component.abc).toBe('def'); - }); - - it("should throw if mixins override each others' statics", () => { - expect(function() { - var Mixin = { - statics: { - abc: 'foo', - }, - }; - React.createClass({ - mixins: [Mixin], - - statics: { - abc: 'bar', - }, - - render: function() { - return ; - }, - }); - }).toThrowError( - 'ReactClass: You are attempting to define `abc` on your component more ' + - 'than once. This conflict may be due to a mixin.', - ); - }); - - it('should throw if mixins override functions in statics', () => { - expect(function() { - var Mixin = { - statics: { - abc: function() { - console.log('foo'); - }, - }, - }; - React.createClass({ - mixins: [Mixin], - - statics: { - abc: function() { - console.log('bar'); - }, - }, - - render: function() { - return ; - }, - }); - }).toThrowError( - 'ReactClass: You are attempting to define `abc` on your component ' + - 'more than once. This conflict may be due to a mixin.', - ); - }); - - it('should warn if the mixin is undefined', () => { - spyOn(console, 'error'); - - React.createClass({ - mixins: [undefined], - - render: function() { - return ; - }, - }); - - expectDev(console.error.calls.count()).toBe(1); - expectDev(console.error.calls.argsFor(0)[0]).toBe( - "Warning: ReactClass: You're attempting to include a mixin that is " + - 'either null or not an object. Check the mixins included by the ' + - 'component, as well as any mixins they include themselves. ' + - 'Expected object but got undefined.', - ); - }); - - it('should warn if the mixin is null', () => { - spyOn(console, 'error'); - - React.createClass({ - mixins: [null], - - render: function() { - return ; - }, - }); - - expectDev(console.error.calls.count()).toBe(1); - expectDev(console.error.calls.argsFor(0)[0]).toBe( - "Warning: ReactClass: You're attempting to include a mixin that is " + - 'either null or not an object. Check the mixins included by the ' + - 'component, as well as any mixins they include themselves. ' + - 'Expected object but got null.', - ); - }); - - it('should warn if an undefined mixin is included in another mixin', () => { - spyOn(console, 'error'); - - var mixinA = { - mixins: [undefined], - }; - - React.createClass({ - mixins: [mixinA], - - render: function() { - return ; - }, - }); - - expectDev(console.error.calls.count()).toBe(1); - expectDev(console.error.calls.argsFor(0)[0]).toBe( - "Warning: ReactClass: You're attempting to include a mixin that is " + - 'either null or not an object. Check the mixins included by the ' + - 'component, as well as any mixins they include themselves. ' + - 'Expected object but got undefined.', - ); - }); - - it('should warn if a null mixin is included in another mixin', () => { - spyOn(console, 'error'); - - var mixinA = { - mixins: [null], - }; - - React.createClass({ - mixins: [mixinA], - - render: function() { - return ; - }, - }); - - expectDev(console.error.calls.count()).toBe(1); - expectDev(console.error.calls.argsFor(0)[0]).toBe( - "Warning: ReactClass: You're attempting to include a mixin that is " + - 'either null or not an object. Check the mixins included by the ' + - 'component, as well as any mixins they include themselves. ' + - 'Expected object but got null.', - ); - }); - - it('should throw if the mixin is a React component', () => { - expect(function() { - React.createClass({ - mixins: [
    ], - - render: function() { - return ; - }, - }); - }).toThrowError( - "ReactClass: You're attempting to use a component as a mixin. " + - 'Instead, just use a regular object.', - ); - }); - - it('should throw if the mixin is a React component class', () => { - expect(function() { - var Component = React.createClass({ - render: function() { - return ; - }, - }); - - React.createClass({ - mixins: [Component], - - render: function() { - return ; - }, - }); - }).toThrowError( - "ReactClass: You're attempting to use a component class or function " + - 'as a mixin. Instead, just use a regular object.', - ); - }); - - it('should have bound the mixin methods to the component', () => { - var mixin = { - mixinFunc: function() { - return this; - }, - }; - - var Component = React.createClass({ - mixins: [mixin], - componentDidMount: function() { - expect(this.mixinFunc()).toBe(this); - }, - render: function() { - return ; - }, - }); - ReactTestUtils.renderIntoDocument(); - }); - - it('should include the mixin keys in even if their values are falsy', () => { - var mixin = { - keyWithNullValue: null, - randomCounter: 0, - }; - - var Component = React.createClass({ - mixins: [mixin], - componentDidMount: function() { - expect(this.randomCounter).toBe(0); - expect(this.keyWithNullValue).toBeNull(); - }, - render: function() { - return ; - }, - }); - ReactTestUtils.renderIntoDocument(); - }); - - it('should work with a null getInitialState return value and a mixin', () => { - var Component; - var instance; - - var Mixin = { - getInitialState: function() { - return {foo: 'bar'}; - }, - }; - Component = React.createClass({ - mixins: [Mixin], - getInitialState: function() { - return null; - }, - render: function() { - return ; - }, - }); - expect(() => - ReactTestUtils.renderIntoDocument()).not.toThrow(); - - instance = ReactTestUtils.renderIntoDocument(); - expect(instance.state).toEqual({foo: 'bar'}); - - // Also the other way round should work - var Mixin2 = { - getInitialState: function() { - return null; - }, - }; - Component = React.createClass({ - mixins: [Mixin2], - getInitialState: function() { - return {foo: 'bar'}; - }, - render: function() { - return ; - }, - }); - expect(() => - ReactTestUtils.renderIntoDocument()).not.toThrow(); - - instance = ReactTestUtils.renderIntoDocument(); - expect(instance.state).toEqual({foo: 'bar'}); - - // Multiple mixins should be fine too - Component = React.createClass({ - mixins: [Mixin, Mixin2], - getInitialState: function() { - return {x: true}; - }, - render: function() { - return ; - }, - }); - expect(() => - ReactTestUtils.renderIntoDocument()).not.toThrow(); - - instance = ReactTestUtils.renderIntoDocument(); - expect(instance.state).toEqual({foo: 'bar', x: true}); - }); -}); diff --git a/src/isomorphic/classic/class/__tests__/ReactClass-test.js b/src/isomorphic/classic/class/__tests__/create-react-class-integration-test.js similarity index 65% rename from src/isomorphic/classic/class/__tests__/ReactClass-test.js rename to src/isomorphic/classic/class/__tests__/create-react-class-integration-test.js index 4ab5ad7dac01e..55fb8c1ed8a49 100644 --- a/src/isomorphic/classic/class/__tests__/ReactClass-test.js +++ b/src/isomorphic/classic/class/__tests__/create-react-class-integration-test.js @@ -11,38 +11,37 @@ 'use strict'; +var PropTypes; var React; var ReactDOM; var ReactTestUtils; +var createReactClass; -describe('ReactClass-spec', () => { +describe('create-react-class-integration', () => { beforeEach(() => { + PropTypes = require('prop-types'); React = require('react'); ReactDOM = require('react-dom'); ReactTestUtils = require('ReactTestUtils'); + var createReactClassFactory = require('create-react-class/factory'); + createReactClass = createReactClassFactory( + React.Component, + React.isValidElement, + require('ReactNoopUpdateQueue'), + ); }); it('should throw when `render` is not specified', () => { expect(function() { - React.createClass({}); + createReactClass({}); }).toThrowError( 'createClass(...): Class specification must implement a `render` method.', ); }); - it('should copy `displayName` onto the Constructor', () => { - var TestComponent = React.createClass({ - render: function() { - return
    ; - }, - }); - - expect(TestComponent.displayName).toBe('TestComponent'); - }); - it('should copy prop types onto the Constructor', () => { var propValidator = jest.fn(); - var TestComponent = React.createClass({ + var TestComponent = createReactClass({ propTypes: { value: propValidator, }, @@ -57,7 +56,7 @@ describe('ReactClass-spec', () => { it('should warn on invalid prop types', () => { spyOn(console, 'error'); - React.createClass({ + createReactClass({ displayName: 'Component', propTypes: { prop: null, @@ -66,8 +65,8 @@ describe('ReactClass-spec', () => { return {this.props.prop}; }, }); - expectDev(console.error.calls.count()).toBe(1); - expectDev(console.error.calls.argsFor(0)[0]).toBe( + expect(console.error.calls.count()).toBe(1); + expect(console.error.calls.argsFor(0)[0]).toBe( 'Warning: Component: prop type `prop` is invalid; ' + 'it must be a function, usually from React.PropTypes.', ); @@ -75,7 +74,7 @@ describe('ReactClass-spec', () => { it('should warn on invalid context types', () => { spyOn(console, 'error'); - React.createClass({ + createReactClass({ displayName: 'Component', contextTypes: { prop: null, @@ -84,8 +83,8 @@ describe('ReactClass-spec', () => { return {this.props.prop}; }, }); - expectDev(console.error.calls.count()).toBe(1); - expectDev(console.error.calls.argsFor(0)[0]).toBe( + expect(console.error.calls.count()).toBe(1); + expect(console.error.calls.argsFor(0)[0]).toBe( 'Warning: Component: context type `prop` is invalid; ' + 'it must be a function, usually from React.PropTypes.', ); @@ -93,7 +92,7 @@ describe('ReactClass-spec', () => { it('should throw on invalid child context types', () => { spyOn(console, 'error'); - React.createClass({ + createReactClass({ displayName: 'Component', childContextTypes: { prop: null, @@ -102,8 +101,8 @@ describe('ReactClass-spec', () => { return {this.props.prop}; }, }); - expectDev(console.error.calls.count()).toBe(1); - expectDev(console.error.calls.argsFor(0)[0]).toBe( + expect(console.error.calls.count()).toBe(1); + expect(console.error.calls.argsFor(0)[0]).toBe( 'Warning: Component: child context type `prop` is invalid; ' + 'it must be a function, usually from React.PropTypes.', ); @@ -112,7 +111,7 @@ describe('ReactClass-spec', () => { it('should warn when mispelling shouldComponentUpdate', () => { spyOn(console, 'error'); - React.createClass({ + createReactClass({ componentShouldUpdate: function() { return false; }, @@ -120,14 +119,14 @@ describe('ReactClass-spec', () => { return
    ; }, }); - expectDev(console.error.calls.count()).toBe(1); - expectDev(console.error.calls.argsFor(0)[0]).toBe( + expect(console.error.calls.count()).toBe(1); + expect(console.error.calls.argsFor(0)[0]).toBe( 'Warning: A component has a method called componentShouldUpdate(). Did you ' + 'mean shouldComponentUpdate()? The name is phrased as a question ' + 'because the function is expected to return a value.', ); - React.createClass({ + createReactClass({ displayName: 'NamedComponent', componentShouldUpdate: function() { return false; @@ -136,8 +135,8 @@ describe('ReactClass-spec', () => { return
    ; }, }); - expectDev(console.error.calls.count()).toBe(2); - expectDev(console.error.calls.argsFor(1)[0]).toBe( + expect(console.error.calls.count()).toBe(2); + expect(console.error.calls.argsFor(1)[0]).toBe( 'Warning: NamedComponent has a method called componentShouldUpdate(). Did you ' + 'mean shouldComponentUpdate()? The name is phrased as a question ' + 'because the function is expected to return a value.', @@ -146,7 +145,7 @@ describe('ReactClass-spec', () => { it('should warn when mispelling componentWillReceiveProps', () => { spyOn(console, 'error'); - React.createClass({ + createReactClass({ componentWillRecieveProps: function() { return false; }, @@ -154,8 +153,8 @@ describe('ReactClass-spec', () => { return
    ; }, }); - expectDev(console.error.calls.count()).toBe(1); - expectDev(console.error.calls.argsFor(0)[0]).toBe( + expect(console.error.calls.count()).toBe(1); + expect(console.error.calls.argsFor(0)[0]).toBe( 'Warning: A component has a method called componentWillRecieveProps(). Did you ' + 'mean componentWillReceiveProps()?', ); @@ -163,7 +162,7 @@ describe('ReactClass-spec', () => { it('should throw if a reserved property is in statics', () => { expect(function() { - React.createClass({ + createReactClass({ statics: { getDefaultProps: function() { return { @@ -188,42 +187,42 @@ describe('ReactClass-spec', () => { xit('should warn when using deprecated non-static spec keys', () => { spyOn(console, 'error'); - React.createClass({ + createReactClass({ mixins: [{}], propTypes: { - foo: React.PropTypes.string, + foo: PropTypes.string, }, contextTypes: { - foo: React.PropTypes.string, + foo: PropTypes.string, }, childContextTypes: { - foo: React.PropTypes.string, + foo: PropTypes.string, }, render: function() { return
    ; }, }); - expectDev(console.error.calls.count()).toBe(4); - expectDev(console.error.calls.argsFor(0)[0]).toBe( + expect(console.error.calls.count()).toBe(4); + expect(console.error.calls.argsFor(0)[0]).toBe( 'createClass(...): `mixins` is now a static property and should ' + 'be defined inside "statics".', ); - expectDev(console.error.calls.argsFor(1)[0]).toBe( + expect(console.error.calls.argsFor(1)[0]).toBe( 'createClass(...): `propTypes` is now a static property and should ' + 'be defined inside "statics".', ); - expectDev(console.error.calls.argsFor(2)[0]).toBe( + expect(console.error.calls.argsFor(2)[0]).toBe( 'createClass(...): `contextTypes` is now a static property and ' + 'should be defined inside "statics".', ); - expectDev(console.error.calls.argsFor(3)[0]).toBe( + expect(console.error.calls.argsFor(3)[0]).toBe( 'createClass(...): `childContextTypes` is now a static property and ' + 'should be defined inside "statics".', ); }); it('should support statics', () => { - var Component = React.createClass({ + var Component = createReactClass({ statics: { abc: 'def', def: 0, @@ -253,7 +252,7 @@ describe('ReactClass-spec', () => { }); it('should work with object getInitialState() return values', () => { - var Component = React.createClass({ + var Component = createReactClass({ getInitialState: function() { return { occupation: 'clown', @@ -269,9 +268,9 @@ describe('ReactClass-spec', () => { }); it('renders based on context getInitialState', () => { - var Foo = React.createClass({ + var Foo = createReactClass({ contextTypes: { - className: React.PropTypes.string, + className: PropTypes.string, }, getInitialState() { return {className: this.context.className}; @@ -281,9 +280,9 @@ describe('ReactClass-spec', () => { }, }); - var Outer = React.createClass({ + var Outer = createReactClass({ childContextTypes: { - className: React.PropTypes.string, + className: PropTypes.string, }, getChildContext() { return {className: 'foo'}; @@ -300,7 +299,7 @@ describe('ReactClass-spec', () => { it('should throw with non-object getInitialState() return values', () => { [['an array'], 'a string', 1234].forEach(function(state) { - var Component = React.createClass({ + var Component = createReactClass({ getInitialState: function() { return state; }, @@ -318,7 +317,7 @@ describe('ReactClass-spec', () => { }); it('should work with a null getInitialState() return value', () => { - var Component = React.createClass({ + var Component = createReactClass({ getInitialState: function() { return null; }, @@ -332,17 +331,97 @@ describe('ReactClass-spec', () => { it('should throw when using legacy factories', () => { spyOn(console, 'error'); - var Component = React.createClass({ + var Component = createReactClass({ render() { return
    ; }, }); expect(() => Component()).toThrow(); - expectDev(console.error.calls.count()).toBe(1); - expectDev(console.error.calls.argsFor(0)[0]).toBe( + expect(console.error.calls.count()).toBe(1); + expect(console.error.calls.argsFor(0)[0]).toBe( 'Warning: Something is calling a React component directly. Use a ' + 'factory or JSX instead. See: https://fb.me/react-legacyfactory', ); }); + + it('replaceState and callback works', () => { + var ops = []; + var Component = createReactClass({ + getInitialState() { + return {step: 0}; + }, + render() { + ops.push('Render: ' + this.state.step); + return
    ; + }, + }); + + var instance = ReactTestUtils.renderIntoDocument(); + instance.replaceState({step: 1}, () => { + ops.push('Callback: ' + instance.state.step); + }); + expect(ops).toEqual(['Render: 0', 'Render: 1', 'Callback: 1']); + }); + + it('isMounted works', () => { + spyOn(console, 'error'); + + var ops = []; + var instance; + var Component = createReactClass({ + displayName: 'MyComponent', + log(name) { + ops.push(`${name}: ${this.isMounted()}`); + }, + getInitialState() { + this.log('getInitialState'); + return {}; + }, + componentWillMount() { + this.log('componentWillMount'); + }, + componentDidMount() { + this.log('componentDidMount'); + }, + componentWillUpdate() { + this.log('componentWillUpdate'); + }, + componentDidUpdate() { + this.log('componentDidUpdate'); + }, + componentWillUnmount() { + this.log('componentWillUnmount'); + }, + render() { + instance = this; + this.log('render'); + return
    ; + }, + }); + + var container = document.createElement('div'); + ReactDOM.render(, container); + ReactDOM.render(, container); + ReactDOM.unmountComponentAtNode(container); + instance.log('after unmount'); + expect(ops).toEqual([ + 'getInitialState: false', + 'componentWillMount: false', + 'render: false', + 'componentDidMount: true', + 'componentWillUpdate: true', + 'render: true', + 'componentDidUpdate: true', + 'componentWillUnmount: false', + 'after unmount: false', + ]); + + expect(console.error.calls.count()).toBe(1); + expect(console.error.calls.argsFor(0)[0]).toEqual( + 'Warning: MyComponent: isMounted is deprecated. Instead, make sure to ' + + 'clean up subscriptions and pending requests in componentWillUnmount ' + + 'to prevent memory leaks.', + ); + }); }); diff --git a/src/umd/ReactDOMServerUMDEntry.js b/src/isomorphic/classic/class/createClass.js similarity index 50% rename from src/umd/ReactDOMServerUMDEntry.js rename to src/isomorphic/classic/class/createClass.js index 7a55540e2b729..3791e5392dc0a 100644 --- a/src/umd/ReactDOMServerUMDEntry.js +++ b/src/isomorphic/classic/class/createClass.js @@ -6,11 +6,14 @@ * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. * - * @providesModule ReactDOMServerUMDEntry + * @providesModule createClass */ 'use strict'; -var ReactDOMServer = require('ReactDOMServer'); +var {Component} = require('ReactBaseClasses'); +var {isValidElement} = require('ReactElement'); +var ReactNoopUpdateQueue = require('ReactNoopUpdateQueue'); +var factory = require('create-react-class/factory'); -module.exports = ReactDOMServer; +module.exports = factory(Component, isValidElement, ReactNoopUpdateQueue); diff --git a/src/isomorphic/classic/element/ReactElementValidator.js b/src/isomorphic/classic/element/ReactElementValidator.js index 324433338385b..9840ba3a59bb8 100644 --- a/src/isomorphic/classic/element/ReactElementValidator.js +++ b/src/isomorphic/classic/element/ReactElementValidator.js @@ -21,13 +21,12 @@ var ReactCurrentOwner = require('ReactCurrentOwner'); var ReactElement = require('ReactElement'); -var checkReactTypeSpec = require('checkReactTypeSpec'); - var canDefineProperty = require('canDefineProperty'); var getComponentName = require('getComponentName'); var getIteratorFn = require('getIteratorFn'); if (__DEV__) { + var checkPropTypes = require('checkPropTypes'); var warning = require('fbjs/lib/warning'); var ReactDebugCurrentFrame = require('ReactDebugCurrentFrame'); var { @@ -193,7 +192,13 @@ function validatePropTypes(element) { : componentClass.propTypes; if (propTypes) { - checkReactTypeSpec(propTypes, element.props, 'prop', name); + checkPropTypes( + propTypes, + element.props, + 'prop', + name, + ReactDebugCurrentFrame.getStackAddendum, + ); } if (typeof componentClass.getDefaultProps === 'function') { warning( diff --git a/src/isomorphic/classic/element/__tests__/ReactElement-test.js b/src/isomorphic/classic/element/__tests__/ReactElement-test.js index 27bd222be8ffd..30a74615feb3c 100644 --- a/src/isomorphic/classic/element/__tests__/ReactElement-test.js +++ b/src/isomorphic/classic/element/__tests__/ReactElement-test.js @@ -34,11 +34,11 @@ describe('ReactElement', () => { ReactDOMFeatureFlags = require('ReactDOMFeatureFlags'); // NOTE: We're explicitly not using JSX here. This is intended to test // classic JS without JSX. - ComponentClass = React.createClass({ - render: function() { + ComponentClass = class extends React.Component { + render() { return React.createElement('div'); - }, - }); + } + }; }); afterEach(() => { @@ -59,37 +59,7 @@ describe('ReactElement', () => { expect(element.props).toEqual({}); }); - it('should warn when `key` is being accessed on createClass element', () => { - spyOn(console, 'error'); - var container = document.createElement('div'); - var Child = React.createClass({ - render: function() { - return
    {this.props.key}
    ; - }, - }); - var Parent = React.createClass({ - render: function() { - return ( -
    - - - -
    - ); - }, - }); - expectDev(console.error.calls.count()).toBe(0); - ReactDOM.render(, container); - expectDev(console.error.calls.count()).toBe(1); - expectDev(console.error.calls.argsFor(0)[0]).toContain( - 'Child: `key` is not a prop. Trying to access it will result ' + - 'in `undefined` being returned. If you need to access the same ' + - 'value within the child component, you should pass it as a different ' + - 'prop. (https://fb.me/react-special-props)', - ); - }); - - it('should warn when `key` is being accessed on ES class element', () => { + it('should warn when `key` is being accessed on composite element', () => { spyOn(console, 'error'); var container = document.createElement('div'); class Child extends React.Component { @@ -97,8 +67,8 @@ describe('ReactElement', () => { return
    {this.props.key}
    ; } } - var Parent = React.createClass({ - render: function() { + class Parent extends React.Component { + render() { return (
    @@ -106,9 +76,9 @@ describe('ReactElement', () => {
    ); - }, - }); - expectDev(console.error.calls.count()).toBe(0); + } + } + expect(console.error.calls.count()).toBe(0); ReactDOM.render(, container); expectDev(console.error.calls.count()).toBe(1); expectDev(console.error.calls.argsFor(0)[0]).toContain( @@ -136,21 +106,21 @@ describe('ReactElement', () => { it('should warn when `ref` is being accessed', () => { spyOn(console, 'error'); var container = document.createElement('div'); - var Child = React.createClass({ - render: function() { + class Child extends React.Component { + render() { return
    {this.props.ref}
    ; - }, - }); - var Parent = React.createClass({ - render: function() { + } + } + class Parent extends React.Component { + render() { return (
    ); - }, - }); - expectDev(console.error.calls.count()).toBe(0); + } + } + expect(console.error.calls.count()).toBe(0); ReactDOM.render(, container); expectDev(console.error.calls.count()).toBe(1); expectDev(console.error.calls.argsFor(0)[0]).toContain( @@ -257,12 +227,12 @@ describe('ReactElement', () => { var Component = React.createFactory(ComponentClass); var element; - var Wrapper = React.createClass({ - render: function() { + class Wrapper extends React.Component { + render() { element = Component(); return element; - }, - }); + } + } var instance = ReactTestUtils.renderIntoDocument( React.createElement(Wrapper), @@ -324,19 +294,12 @@ describe('ReactElement', () => { it('allows static methods to be called using the type property', () => { spyOn(console, 'error'); - var StaticMethodComponentClass = React.createClass({ - statics: { - someStaticMethod: function() { - return 'someReturnValue'; - }, - }, - getInitialState: function() { - return {valueToReturn: 'hi'}; - }, - render: function() { + class StaticMethodComponentClass extends React.Component { + render() { return React.createElement('div'); - }, - }); + } + } + StaticMethodComponentClass.someStaticMethod = () => 'someReturnValue'; var element = React.createElement(StaticMethodComponentClass); expect(element.type.someStaticMethod()).toBe('someReturnValue'); @@ -346,11 +309,11 @@ describe('ReactElement', () => { // NOTE: We're explicitly not using JSX here. This is intended to test // classic JS without JSX. it('identifies valid elements', () => { - var Component = React.createClass({ - render: function() { + class Component extends React.Component { + render() { return React.createElement('div'); - }, - }); + } + } expect(React.isValidElement(React.createElement('div'))).toEqual(true); expect(React.isValidElement(React.createElement(Component))).toEqual(true); @@ -367,21 +330,6 @@ describe('ReactElement', () => { expect(React.isValidElement(JSON.parse(jsonElement))).toBe(true); }); - it('allows the use of PropTypes validators in statics', () => { - // TODO: This test was added to cover a special case where we proxied - // methods. However, we don't do that any more so this test can probably - // be removed. Leaving it in classic as a safety precaution. - var Component = React.createClass({ - render: () => null, - statics: { - specialType: React.PropTypes.shape({monkey: React.PropTypes.any}), - }, - }); - - expect(typeof Component.specialType).toBe('function'); - expect(typeof Component.specialType.isRequired).toBe('function'); - }); - // NOTE: We're explicitly not using JSX here. This is intended to test // classic JS without JSX. it('is indistinguishable from a plain object', () => { @@ -393,14 +341,12 @@ describe('ReactElement', () => { // NOTE: We're explicitly not using JSX here. This is intended to test // classic JS without JSX. it('should use default prop value when removing a prop', () => { - var Component = React.createClass({ - getDefaultProps: function() { - return {fruit: 'persimmon'}; - }, - render: function() { + class Component extends React.Component { + render() { return React.createElement('span'); - }, - }); + } + } + Component.defaultProps = {fruit: 'persimmon'}; var container = document.createElement('div'); var instance = ReactDOM.render( @@ -416,14 +362,12 @@ describe('ReactElement', () => { // NOTE: We're explicitly not using JSX here. This is intended to test // classic JS without JSX. it('should normalize props with default values', () => { - var Component = React.createClass({ - getDefaultProps: function() { - return {prop: 'testKey'}; - }, - render: function() { + class Component extends React.Component { + render() { return React.createElement('span', null, this.props.prop); - }, - }); + } + } + Component.defaultProps = {prop: 'testKey'}; var instance = ReactTestUtils.renderIntoDocument( React.createElement(Component), @@ -437,8 +381,8 @@ describe('ReactElement', () => { }); it('throws when changing a prop (in dev) after element creation', () => { - var Outer = React.createClass({ - render: function() { + class Outer extends React.Component { + render() { var el =
    ; expect(function() { @@ -447,17 +391,16 @@ describe('ReactElement', () => { expect(el.props.className).toBe('moo'); return el; - }, - }); + } + } var outer = ReactTestUtils.renderIntoDocument(); expect(ReactDOM.findDOMNode(outer).className).toBe('moo'); }); it('throws when adding a prop (in dev) after element creation', () => { var container = document.createElement('div'); - var Outer = React.createClass({ - getDefaultProps: () => ({sound: 'meow'}), - render: function() { + class Outer extends React.Component { + render() { var el =
    {this.props.sound}
    ; expect(function() { @@ -467,8 +410,9 @@ describe('ReactElement', () => { expect(el.props.className).toBe(undefined); return el; - }, - }); + } + } + Outer.defaultProps = {sound: 'meow'}; var outer = ReactDOM.render(, container); expect(ReactDOM.findDOMNode(outer).textContent).toBe('meow'); expect(ReactDOM.findDOMNode(outer).className).toBe(''); @@ -476,11 +420,11 @@ describe('ReactElement', () => { it('does not warn for NaN props', () => { spyOn(console, 'error'); - var Test = React.createClass({ - render: function() { + class Test extends React.Component { + render() { return
    ; - }, - }); + } + } var test = ReactTestUtils.renderIntoDocument(); expect(test.props.value).toBeNaN(); expectDev(console.error.calls.count()).toBe(0); @@ -508,11 +452,11 @@ describe('ReactElement', () => { React = require('react'); - var Component = React.createClass({ - render: function() { + class Component extends React.Component { + render() { return React.createElement('div'); - }, - }); + } + } expect(React.isValidElement(React.createElement('div'))).toEqual(true); expect(React.isValidElement(React.createElement(Component))).toEqual(true); @@ -544,15 +488,15 @@ describe('comparing jsx vs .createFactory() vs .createElement()', () => { describe('when using jsx only', () => { var Parent, instance; beforeEach(() => { - Parent = React.createClass({ - render: function() { + Parent = class extends React.Component { + render() { return (
    children value
    ); - }, - }); + } + }; instance = ReactTestUtils.renderIntoDocument(); }); @@ -580,14 +524,14 @@ describe('comparing jsx vs .createFactory() vs .createElement()', () => { var factory, instance; beforeEach(() => { var childFactory = React.createFactory(Child); - var Parent = React.createClass({ - render: function() { + class Parent extends React.Component { + render() { return React.DOM.div( {}, childFactory({ref: 'child', foo: 'foo value'}, 'children value'), ); - }, - }); + } + } factory = React.createFactory(Parent); instance = ReactTestUtils.renderIntoDocument(factory()); }); @@ -615,8 +559,8 @@ describe('comparing jsx vs .createFactory() vs .createElement()', () => { describe('when using parent that uses .createElement()', () => { var factory, instance; beforeEach(() => { - var Parent = React.createClass({ - render: function() { + class Parent extends React.Component { + render() { return React.DOM.div( {}, React.createElement( @@ -625,8 +569,8 @@ describe('comparing jsx vs .createFactory() vs .createElement()', () => { 'children value', ), ); - }, - }); + } + } factory = React.createFactory(Parent); instance = ReactTestUtils.renderIntoDocument(factory()); }); diff --git a/src/isomorphic/classic/element/__tests__/ReactElementClone-test.js b/src/isomorphic/classic/element/__tests__/ReactElementClone-test.js index b4eaaa0765ef7..365b187d24391 100644 --- a/src/isomorphic/classic/element/__tests__/ReactElementClone-test.js +++ b/src/isomorphic/classic/element/__tests__/ReactElementClone-test.js @@ -11,6 +11,7 @@ 'use strict'; +var PropTypes; var React; var ReactDOM; var ReactTestUtils; @@ -19,58 +20,59 @@ describe('ReactElementClone', () => { var ComponentClass; beforeEach(() => { + PropTypes = require('prop-types'); React = require('react'); ReactDOM = require('react-dom'); ReactTestUtils = require('ReactTestUtils'); // NOTE: We're explicitly not using JSX here. This is intended to test // classic JS without JSX. - ComponentClass = React.createClass({ - render: function() { + ComponentClass = class extends React.Component { + render() { return React.createElement('div'); - }, - }); + } + }; }); it('should clone a DOM component with new props', () => { - var Grandparent = React.createClass({ - render: function() { + class Grandparent extends React.Component { + render() { return } />; - }, - }); - var Parent = React.createClass({ - render: function() { + } + } + class Parent extends React.Component { + render() { return (
    {React.cloneElement(this.props.child, {className: 'xyz'})}
    ); - }, - }); + } + } var component = ReactTestUtils.renderIntoDocument(); expect(ReactDOM.findDOMNode(component).childNodes[0].className).toBe('xyz'); }); it('should clone a composite component with new props', () => { - var Child = React.createClass({ - render: function() { + class Child extends React.Component { + render() { return
    ; - }, - }); - var Grandparent = React.createClass({ - render: function() { + } + } + class Grandparent extends React.Component { + render() { return } />; - }, - }); - var Parent = React.createClass({ - render: function() { + } + } + class Parent extends React.Component { + render() { return (
    {React.cloneElement(this.props.child, {className: 'xyz'})}
    ); - }, - }); + } + } var component = ReactTestUtils.renderIntoDocument(); expect(ReactDOM.findDOMNode(component).childNodes[0].className).toBe('xyz'); }); @@ -81,43 +83,43 @@ describe('ReactElementClone', () => { }); it('should keep the original ref if it is not overridden', () => { - var Grandparent = React.createClass({ - render: function() { + class Grandparent extends React.Component { + render() { return } />; - }, - }); + } + } - var Parent = React.createClass({ - render: function() { + class Parent extends React.Component { + render() { return (
    {React.cloneElement(this.props.child, {className: 'xyz'})}
    ); - }, - }); + } + } var component = ReactTestUtils.renderIntoDocument(); expect(component.refs.yolo.tagName).toBe('DIV'); }); it('should transfer the key property', () => { - var Component = React.createClass({ - render: function() { + class Component extends React.Component { + render() { return null; - }, - }); + } + } var clone = React.cloneElement(, {key: 'xyz'}); expect(clone.key).toBe('xyz'); }); it('should transfer children', () => { - var Component = React.createClass({ - render: function() { + class Component extends React.Component { + render() { expect(this.props.children).toBe('xyz'); return
    ; - }, - }); + } + } ReactTestUtils.renderIntoDocument( React.cloneElement(, {children: 'xyz'}), @@ -125,12 +127,12 @@ describe('ReactElementClone', () => { }); it('should shallow clone children', () => { - var Component = React.createClass({ - render: function() { + class Component extends React.Component { + render() { expect(this.props.children).toBe('xyz'); return
    ; - }, - }); + } + } ReactTestUtils.renderIntoDocument( React.cloneElement(xyz, {}), @@ -138,11 +140,11 @@ describe('ReactElementClone', () => { }); it('should accept children as rest arguments', () => { - var Component = React.createClass({ - render: function() { + class Component extends React.Component { + render() { return null; - }, - }); + } + } var clone = React.cloneElement( xyz, @@ -175,8 +177,8 @@ describe('ReactElementClone', () => { }); it('should support keys and refs', () => { - var Parent = React.createClass({ - render: function() { + class Parent extends React.Component { + render() { var clone = React.cloneElement(this.props.children, { key: 'xyz', ref: 'xyz', @@ -184,32 +186,32 @@ describe('ReactElementClone', () => { expect(clone.key).toBe('xyz'); expect(clone.ref).toBe('xyz'); return
    {clone}
    ; - }, - }); + } + } - var Grandparent = React.createClass({ - render: function() { + class Grandparent extends React.Component { + render() { return ; - }, - }); + } + } var component = ReactTestUtils.renderIntoDocument(); expect(component.refs.parent.refs.xyz.tagName).toBe('SPAN'); }); it('should steal the ref if a new ref is specified', () => { - var Parent = React.createClass({ - render: function() { + class Parent extends React.Component { + render() { var clone = React.cloneElement(this.props.children, {ref: 'xyz'}); return
    {clone}
    ; - }, - }); + } + } - var Grandparent = React.createClass({ - render: function() { + class Grandparent extends React.Component { + render() { return ; - }, - }); + } + } var component = ReactTestUtils.renderIntoDocument(); expect(component.refs.child).toBeUndefined(); @@ -217,12 +219,12 @@ describe('ReactElementClone', () => { }); it('should overwrite props', () => { - var Component = React.createClass({ - render: function() { + class Component extends React.Component { + render() { expect(this.props.myprop).toBe('xyz'); return
    ; - }, - }); + } + } ReactTestUtils.renderIntoDocument( React.cloneElement(, {myprop: 'xyz'}), @@ -230,14 +232,12 @@ describe('ReactElementClone', () => { }); it('should normalize props with default values', () => { - var Component = React.createClass({ - getDefaultProps: function() { - return {prop: 'testKey'}; - }, - render: function() { + class Component extends React.Component { + render() { return ; - }, - }); + } + } + Component.defaultProps = {prop: 'testKey'}; var instance = React.createElement(Component); var clonedInstance = React.cloneElement(instance, {prop: undefined}); @@ -289,26 +289,26 @@ describe('ReactElementClone', () => { it('should check declared prop types after clone', () => { spyOn(console, 'error'); - var Component = React.createClass({ - propTypes: { - color: React.PropTypes.string.isRequired, - }, - render: function() { + class Component extends React.Component { + static propTypes = { + color: PropTypes.string.isRequired, + }; + render() { return React.createElement('div', null, 'My color is ' + this.color); - }, - }); - var Parent = React.createClass({ - render: function() { + } + } + class Parent extends React.Component { + render() { return React.cloneElement(this.props.child, {color: 123}); - }, - }); - var GrandParent = React.createClass({ - render: function() { + } + } + class GrandParent extends React.Component { + render() { return React.createElement(Parent, { child: React.createElement(Component, {color: 'red'}), }); - }, - }); + } + } ReactTestUtils.renderIntoDocument(React.createElement(GrandParent)); expectDev(console.error.calls.count()).toBe(1); expectDev(console.error.calls.argsFor(0)[0]).toBe( diff --git a/src/isomorphic/classic/element/__tests__/ReactElementValidator-test.js b/src/isomorphic/classic/element/__tests__/ReactElementValidator-test.js index c9dbf7cc8f8ed..69bb8fd512b8d 100644 --- a/src/isomorphic/classic/element/__tests__/ReactElementValidator-test.js +++ b/src/isomorphic/classic/element/__tests__/ReactElementValidator-test.js @@ -14,6 +14,7 @@ // NOTE: We're explicitly not using JSX in this file. This is intended to test // classic JS without JSX. +var PropTypes; var React; var ReactDOM; var ReactTestUtils; @@ -28,14 +29,15 @@ describe('ReactElementValidator', () => { beforeEach(() => { jest.resetModules(); + PropTypes = require('prop-types'); React = require('react'); ReactDOM = require('react-dom'); ReactTestUtils = require('ReactTestUtils'); - ComponentClass = React.createClass({ - render: function() { + ComponentClass = class extends React.Component { + render() { return React.createElement('div'); - }, - }); + } + }; }); it('warns for keys for arrays of elements in rest args', () => { @@ -54,21 +56,19 @@ describe('ReactElementValidator', () => { spyOn(console, 'error'); var Component = React.createFactory(ComponentClass); - var InnerClass = React.createClass({ - displayName: 'InnerClass', - render: function() { + class InnerClass extends React.Component { + render() { return Component(null, this.props.childSet); - }, - }); + } + } var InnerComponent = React.createFactory(InnerClass); - var ComponentWrapper = React.createClass({ - displayName: 'ComponentWrapper', - render: function() { + class ComponentWrapper extends React.Component { + render() { return InnerComponent({childSet: [Component(), Component()]}); - }, - }); + } + } ReactTestUtils.renderIntoDocument(React.createElement(ComponentWrapper)); @@ -83,12 +83,10 @@ describe('ReactElementValidator', () => { it('warns for keys for arrays with no owner or parent info', () => { spyOn(console, 'error'); - var Anonymous = React.createClass({ - displayName: undefined, - render: function() { - return
    ; - }, - }); + function Anonymous() { + return
    ; + } + Object.defineProperty(Anonymous, 'name', {value: undefined}); var divs = [
    ,
    ]; ReactTestUtils.renderIntoDocument({divs}); @@ -119,23 +117,17 @@ describe('ReactElementValidator', () => { it('warns for keys with component stack info', () => { spyOn(console, 'error'); - var Component = React.createClass({ - render: function() { - return
    {[
    ,
    ]}
    ; - }, - }); + function Component() { + return
    {[
    ,
    ]}
    ; + } - var Parent = React.createClass({ - render: function() { - return React.cloneElement(this.props.child); - }, - }); + function Parent(props) { + return React.cloneElement(props.child); + } - var GrandParent = React.createClass({ - render: function() { - return } />; - }, - }); + function GrandParent() { + return } />; + } ReactTestUtils.renderIntoDocument(); @@ -154,16 +146,14 @@ describe('ReactElementValidator', () => { it('does not warn for keys when passing children down', () => { spyOn(console, 'error'); - var Wrapper = React.createClass({ - render: function() { - return ( -
    - {this.props.children} -
    -
    - ); - }, - }); + function Wrapper(props) { + return ( +
    + {props.children} +
    +
    + ); + } ReactTestUtils.renderIntoDocument( @@ -255,19 +245,15 @@ describe('ReactElementValidator', () => { // component, we give a small hint as to which parent instantiated that // component as per warnings about key usage in ReactElementValidator. spyOn(console, 'error'); - var MyComp = React.createClass({ - propTypes: { - color: React.PropTypes.string, - }, - render: function() { - return React.createElement('div', null, 'My color is ' + this.color); - }, - }); - var ParentComp = React.createClass({ - render: function() { - return React.createElement(MyComp, {color: 123}); - }, - }); + function MyComp(props) { + return React.createElement('div', null, 'My color is ' + props.color); + } + MyComp.propTypes = { + color: PropTypes.string, + }; + function ParentComp() { + return React.createElement(MyComp, {color: 123}); + } ReactTestUtils.renderIntoDocument(React.createElement(ParentComp)); expectDev(console.error.calls.argsFor(0)[0]).toBe( 'Warning: Failed prop type: ' + @@ -325,11 +311,9 @@ describe('ReactElementValidator', () => { it('includes the owner name when passing null, undefined, boolean, or number', () => { spyOn(console, 'error'); - var ParentComp = React.createClass({ - render: function() { - return React.createElement(null); - }, - }); + function ParentComp() { + return React.createElement(null); + } expect(function() { ReactTestUtils.renderIntoDocument(React.createElement(ParentComp)); }).toThrowError( @@ -349,15 +333,13 @@ describe('ReactElementValidator', () => { it('should check default prop values', () => { spyOn(console, 'error'); - var Component = React.createClass({ - propTypes: {prop: React.PropTypes.string.isRequired}, - getDefaultProps: function() { - return {prop: null}; - }, - render: function() { + class Component extends React.Component { + static propTypes = {prop: PropTypes.string.isRequired}; + static defaultProps = {prop: null}; + render() { return React.createElement('span', null, this.props.prop); - }, - }); + } + } ReactTestUtils.renderIntoDocument(React.createElement(Component)); @@ -372,15 +354,13 @@ describe('ReactElementValidator', () => { it('should not check the default for explicit null', () => { spyOn(console, 'error'); - var Component = React.createClass({ - propTypes: {prop: React.PropTypes.string.isRequired}, - getDefaultProps: function() { - return {prop: 'text'}; - }, - render: function() { + class Component extends React.Component { + static propTypes = {prop: PropTypes.string.isRequired}; + static defaultProps = {prop: 'text'}; + render() { return React.createElement('span', null, this.props.prop); - }, - }); + } + } ReactTestUtils.renderIntoDocument( React.createElement(Component, {prop: null}), @@ -397,14 +377,14 @@ describe('ReactElementValidator', () => { it('should check declared prop types', () => { spyOn(console, 'error'); - var Component = React.createClass({ - propTypes: { - prop: React.PropTypes.string.isRequired, - }, - render: function() { + class Component extends React.Component { + static propTypes = { + prop: PropTypes.string.isRequired, + }; + render() { return React.createElement('span', null, this.props.prop); - }, - }); + } + } ReactTestUtils.renderIntoDocument(React.createElement(Component)); ReactTestUtils.renderIntoDocument( @@ -437,14 +417,14 @@ describe('ReactElementValidator', () => { it('should warn if a PropType creator is used as a PropType', () => { spyOn(console, 'error'); - var Component = React.createClass({ - propTypes: { - myProp: React.PropTypes.shape, - }, - render: function() { + class Component extends React.Component { + static propTypes = { + myProp: PropTypes.shape, + }; + render() { return React.createElement('span', null, this.props.myProp.value); - }, - }); + } + } ReactTestUtils.renderIntoDocument( React.createElement(Component, {myProp: {value: 'hi'}}), @@ -462,11 +442,9 @@ describe('ReactElementValidator', () => { it('should warn when accessing .type on an element factory', () => { spyOn(console, 'error'); - var TestComponent = React.createClass({ - render: function() { - return
    ; - }, - }); + function TestComponent() { + return
    ; + } var TestFactory = React.createFactory(TestComponent); expect(TestFactory.type).toBe(TestComponent); expectDev(console.error.calls.count()).toBe(1); @@ -481,14 +459,14 @@ describe('ReactElementValidator', () => { it('does not warn when using DOM node as children', () => { spyOn(console, 'error'); - var DOMContainer = React.createClass({ - render: function() { + class DOMContainer extends React.Component { + render() { return
    ; - }, - componentDidMount: function() { + } + componentDidMount() { ReactDOM.findDOMNode(this).appendChild(this.props.children); - }, - }); + } + } var node = document.createElement('div'); // This shouldn't cause a stack overflow or any other problems (#3883) diff --git a/src/isomorphic/classic/types/ReactPropTypes.js b/src/isomorphic/classic/types/ReactPropTypes.js index 01710a0086694..5c0ca77351039 100644 --- a/src/isomorphic/classic/types/ReactPropTypes.js +++ b/src/isomorphic/classic/types/ReactPropTypes.js @@ -11,548 +11,7 @@ 'use strict'; -var ReactElement = require('ReactElement'); -var ReactPropTypesSecret = require('ReactPropTypesSecret'); +var {isValidElement} = require('ReactElement'); +var factory = require('prop-types/factory'); -var emptyFunction = require('fbjs/lib/emptyFunction'); -var getIteratorFn = require('getIteratorFn'); -var invariant = require('fbjs/lib/invariant'); -var warning = require('fbjs/lib/warning'); - -/** - * Collection of methods that allow declaration and validation of props that are - * supplied to React components. Example usage: - * - * var Props = require('ReactPropTypes'); - * var MyArticle = React.createClass({ - * propTypes: { - * // An optional string prop named "description". - * description: Props.string, - * - * // A required enum prop named "category". - * category: Props.oneOf(['News','Photos']).isRequired, - * - * // A prop named "dialog" that requires an instance of Dialog. - * dialog: Props.instanceOf(Dialog).isRequired - * }, - * render: function() { ... } - * }); - * - * A more formal specification of how these methods are used: - * - * type := array|bool|func|object|number|string|oneOf([...])|instanceOf(...) - * decl := ReactPropTypes.{type}(.isRequired)? - * - * Each and every declaration produces a function with the same signature. This - * allows the creation of custom validation functions. For example: - * - * var MyLink = React.createClass({ - * propTypes: { - * // An optional string or URI prop named "href". - * href: function(props, propName, componentName) { - * var propValue = props[propName]; - * if (propValue != null && typeof propValue !== 'string' && - * !(propValue instanceof URI)) { - * return new Error( - * 'Expected a string or an URI for ' + propName + ' in ' + - * componentName - * ); - * } - * } - * }, - * render: function() {...} - * }); - * - * @internal - */ - -var ANONYMOUS = '<>'; - -var ReactPropTypes; - -if (__DEV__) { - // Keep in sync with production version below - ReactPropTypes = { - array: createPrimitiveTypeChecker('array'), - bool: createPrimitiveTypeChecker('boolean'), - func: createPrimitiveTypeChecker('function'), - number: createPrimitiveTypeChecker('number'), - object: createPrimitiveTypeChecker('object'), - string: createPrimitiveTypeChecker('string'), - symbol: createPrimitiveTypeChecker('symbol'), - - any: createAnyTypeChecker(), - arrayOf: createArrayOfTypeChecker, - element: createElementTypeChecker(), - instanceOf: createInstanceTypeChecker, - node: createNodeChecker(), - objectOf: createObjectOfTypeChecker, - oneOf: createEnumTypeChecker, - oneOfType: createUnionTypeChecker, - shape: createShapeTypeChecker, - }; -} else { - var productionTypeChecker = function() { - invariant( - false, - 'React.PropTypes type checking code is stripped in production.', - ); - }; - productionTypeChecker.isRequired = productionTypeChecker; - var getProductionTypeChecker = () => productionTypeChecker; - // Keep in sync with development version above - ReactPropTypes = { - array: productionTypeChecker, - bool: productionTypeChecker, - func: productionTypeChecker, - number: productionTypeChecker, - object: productionTypeChecker, - string: productionTypeChecker, - symbol: productionTypeChecker, - - any: productionTypeChecker, - arrayOf: getProductionTypeChecker, - element: productionTypeChecker, - instanceOf: getProductionTypeChecker, - node: productionTypeChecker, - objectOf: getProductionTypeChecker, - oneOf: getProductionTypeChecker, - oneOfType: getProductionTypeChecker, - shape: getProductionTypeChecker, - }; -} - -/** - * inlined Object.is polyfill to avoid requiring consumers ship their own - * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is - */ -/*eslint-disable no-self-compare*/ -function is(x, y) { - // SameValue algorithm - if (x === y) { - // Steps 1-5, 7-10 - // Steps 6.b-6.e: +0 != -0 - return x !== 0 || 1 / x === 1 / y; - } else { - // Step 6.a: NaN == NaN - return x !== x && y !== y; - } -} -/*eslint-enable no-self-compare*/ - -/** - * We use an Error-like object for backward compatibility as people may call - * PropTypes directly and inspect their output. However, we don't use real - * Errors anymore. We don't inspect their stack anyway, and creating them - * is prohibitively expensive if they are created too often, such as what - * happens in oneOfType() for any type before the one that matched. - */ -function PropTypeError(message) { - this.message = message; - this.stack = ''; -} -// Make `instanceof Error` still work for returned errors. -PropTypeError.prototype = Error.prototype; - -function createChainableTypeChecker(validate) { - if (__DEV__) { - var manualPropTypeCallCache = {}; - } - function checkType( - isRequired, - props, - propName, - componentName, - location, - propFullName, - secret, - ) { - componentName = componentName || ANONYMOUS; - propFullName = propFullName || propName; - if (__DEV__) { - if (secret !== ReactPropTypesSecret && typeof console !== 'undefined') { - var cacheKey = `${componentName}:${propName}`; - if (!manualPropTypeCallCache[cacheKey]) { - warning( - false, - 'You are manually calling a React.PropTypes validation ' + - 'function for the `%s` prop on `%s`. This is deprecated ' + - 'and will not work in production with the next major version. ' + - 'You may be seeing this warning due to a third-party PropTypes ' + - 'library. See https://fb.me/react-warning-dont-call-proptypes ' + - 'for details.', - propFullName, - componentName, - ); - manualPropTypeCallCache[cacheKey] = true; - } - } - } - if (props[propName] == null) { - if (isRequired) { - if (props[propName] === null) { - return new PropTypeError( - `The ${location} \`${propFullName}\` is marked as required ` + - `in \`${componentName}\`, but its value is \`null\`.`, - ); - } - return new PropTypeError( - `The ${location} \`${propFullName}\` is marked as required in ` + - `\`${componentName}\`, but its value is \`undefined\`.`, - ); - } - return null; - } else { - return validate(props, propName, componentName, location, propFullName); - } - } - - var chainedCheckType = checkType.bind(null, false); - chainedCheckType.isRequired = checkType.bind(null, true); - - return chainedCheckType; -} - -function createPrimitiveTypeChecker(expectedType) { - function validate( - props, - propName, - componentName, - location, - propFullName, - secret, - ) { - var propValue = props[propName]; - var propType = getPropType(propValue); - if (propType !== expectedType) { - // `propValue` being instance of, say, date/regexp, pass the 'object' - // check, but we can offer a more precise error message here rather than - // 'of type `object`'. - var preciseType = getPreciseType(propValue); - - return new PropTypeError( - `Invalid ${location} \`${propFullName}\` of type ` + - `\`${preciseType}\` supplied to \`${componentName}\`, expected ` + - `\`${expectedType}\`.`, - ); - } - return null; - } - return createChainableTypeChecker(validate); -} - -function createAnyTypeChecker() { - return createChainableTypeChecker(emptyFunction.thatReturnsNull); -} - -function createArrayOfTypeChecker(typeChecker) { - function validate(props, propName, componentName, location, propFullName) { - if (typeof typeChecker !== 'function') { - return new PropTypeError( - `Property \`${propFullName}\` of component \`${componentName}\` has invalid PropType notation inside arrayOf.`, - ); - } - var propValue = props[propName]; - if (!Array.isArray(propValue)) { - var propType = getPropType(propValue); - return new PropTypeError( - `Invalid ${location} \`${propFullName}\` of type ` + - `\`${propType}\` supplied to \`${componentName}\`, expected an array.`, - ); - } - for (var i = 0; i < propValue.length; i++) { - var error = typeChecker( - propValue, - i, - componentName, - location, - `${propFullName}[${i}]`, - ReactPropTypesSecret, - ); - if (error instanceof Error) { - return error; - } - } - return null; - } - return createChainableTypeChecker(validate); -} - -function createElementTypeChecker() { - function validate(props, propName, componentName, location, propFullName) { - var propValue = props[propName]; - if (!ReactElement.isValidElement(propValue)) { - var propType = getPropType(propValue); - return new PropTypeError( - `Invalid ${location} \`${propFullName}\` of type ` + - `\`${propType}\` supplied to \`${componentName}\`, expected a single ReactElement.`, - ); - } - return null; - } - return createChainableTypeChecker(validate); -} - -function createInstanceTypeChecker(expectedClass) { - function validate(props, propName, componentName, location, propFullName) { - if (!(props[propName] instanceof expectedClass)) { - var expectedClassName = expectedClass.name || ANONYMOUS; - var actualClassName = getClassName(props[propName]); - return new PropTypeError( - `Invalid ${location} \`${propFullName}\` of type ` + - `\`${actualClassName}\` supplied to \`${componentName}\`, expected ` + - `instance of \`${expectedClassName}\`.`, - ); - } - return null; - } - return createChainableTypeChecker(validate); -} - -function createEnumTypeChecker(expectedValues) { - if (!Array.isArray(expectedValues)) { - warning( - false, - 'Invalid argument supplied to oneOf, expected an instance of array.', - ); - return emptyFunction.thatReturnsNull; - } - - function validate(props, propName, componentName, location, propFullName) { - var propValue = props[propName]; - for (var i = 0; i < expectedValues.length; i++) { - if (is(propValue, expectedValues[i])) { - return null; - } - } - - var valuesString = JSON.stringify(expectedValues); - return new PropTypeError( - `Invalid ${location} \`${propFullName}\` of value \`${propValue}\` ` + - `supplied to \`${componentName}\`, expected one of ${valuesString}.`, - ); - } - return createChainableTypeChecker(validate); -} - -function createObjectOfTypeChecker(typeChecker) { - function validate(props, propName, componentName, location, propFullName) { - if (typeof typeChecker !== 'function') { - return new PropTypeError( - `Property \`${propFullName}\` of component \`${componentName}\` has invalid PropType notation inside objectOf.`, - ); - } - var propValue = props[propName]; - var propType = getPropType(propValue); - if (propType !== 'object') { - return new PropTypeError( - `Invalid ${location} \`${propFullName}\` of type ` + - `\`${propType}\` supplied to \`${componentName}\`, expected an object.`, - ); - } - for (var key in propValue) { - if (propValue.hasOwnProperty(key)) { - var error = typeChecker( - propValue, - key, - componentName, - location, - `${propFullName}.${key}`, - ReactPropTypesSecret, - ); - if (error instanceof Error) { - return error; - } - } - } - return null; - } - return createChainableTypeChecker(validate); -} - -function createUnionTypeChecker(arrayOfTypeCheckers) { - if (!Array.isArray(arrayOfTypeCheckers)) { - warning( - false, - 'Invalid argument supplied to oneOfType, expected an instance of array.', - ); - return emptyFunction.thatReturnsNull; - } - - function validate(props, propName, componentName, location, propFullName) { - for (var i = 0; i < arrayOfTypeCheckers.length; i++) { - var checker = arrayOfTypeCheckers[i]; - if ( - checker( - props, - propName, - componentName, - location, - propFullName, - ReactPropTypesSecret, - ) == null - ) { - return null; - } - } - - return new PropTypeError( - `Invalid ${location} \`${propFullName}\` supplied to ` + - `\`${componentName}\`.`, - ); - } - return createChainableTypeChecker(validate); -} - -function createNodeChecker() { - function validate(props, propName, componentName, location, propFullName) { - if (!isNode(props[propName])) { - return new PropTypeError( - `Invalid ${location} \`${propFullName}\` supplied to ` + - `\`${componentName}\`, expected a ReactNode.`, - ); - } - return null; - } - return createChainableTypeChecker(validate); -} - -function createShapeTypeChecker(shapeTypes) { - function validate(props, propName, componentName, location, propFullName) { - var propValue = props[propName]; - var propType = getPropType(propValue); - if (propType !== 'object') { - return new PropTypeError( - `Invalid ${location} \`${propFullName}\` of type \`${propType}\` ` + - `supplied to \`${componentName}\`, expected \`object\`.`, - ); - } - for (var key in shapeTypes) { - var checker = shapeTypes[key]; - if (!checker) { - continue; - } - var error = checker( - propValue, - key, - componentName, - location, - `${propFullName}.${key}`, - ReactPropTypesSecret, - ); - if (error) { - return error; - } - } - return null; - } - return createChainableTypeChecker(validate); -} - -function isNode(propValue) { - switch (typeof propValue) { - case 'number': - case 'string': - case 'undefined': - return true; - case 'boolean': - return !propValue; - case 'object': - if (Array.isArray(propValue)) { - return propValue.every(isNode); - } - if (propValue === null || ReactElement.isValidElement(propValue)) { - return true; - } - - var iteratorFn = getIteratorFn(propValue); - if (iteratorFn) { - var iterator = iteratorFn.call(propValue); - var step; - if (iteratorFn !== propValue.entries) { - while (!(step = iterator.next()).done) { - if (!isNode(step.value)) { - return false; - } - } - } else { - // Iterator will provide entry [k,v] tuples rather than values. - while (!(step = iterator.next()).done) { - var entry = step.value; - if (entry) { - if (!isNode(entry[1])) { - return false; - } - } - } - } - } else { - return false; - } - - return true; - default: - return false; - } -} - -function isSymbol(propType, propValue) { - // Native Symbol. - if (propType === 'symbol') { - return true; - } - - // 19.4.3.5 Symbol.prototype[@@toStringTag] === 'Symbol' - if (propValue['@@toStringTag'] === 'Symbol') { - return true; - } - - // Fallback for non-spec compliant Symbols which are polyfilled. - if (typeof Symbol === 'function' && propValue instanceof Symbol) { - return true; - } - - return false; -} - -// Equivalent of `typeof` but with special handling for array and regexp. -function getPropType(propValue) { - var propType = typeof propValue; - if (Array.isArray(propValue)) { - return 'array'; - } - if (propValue instanceof RegExp) { - // Old webkits (at least until Android 4.0) return 'function' rather than - // 'object' for typeof a RegExp. We'll normalize this here so that /bla/ - // passes PropTypes.object. - return 'object'; - } - if (isSymbol(propType, propValue)) { - return 'symbol'; - } - return propType; -} - -// This handles more types than `getPropType`. Only used for error messages. -// See `createPrimitiveTypeChecker`. -function getPreciseType(propValue) { - var propType = getPropType(propValue); - if (propType === 'object') { - if (propValue instanceof Date) { - return 'date'; - } else if (propValue instanceof RegExp) { - return 'regexp'; - } - } - return propType; -} - -// Returns class name of the object, if any. -function getClassName(propValue) { - if (!propValue.constructor || !propValue.constructor.name) { - return ANONYMOUS; - } - return propValue.constructor.name; -} - -module.exports = ReactPropTypes; +module.exports = factory(isValidElement); diff --git a/src/isomorphic/classic/types/__tests__/ReactPropTypes-test.js b/src/isomorphic/classic/types/__tests__/ReactPropTypes-test.js index 74059935308c9..edcaca7a6f9a0 100644 --- a/src/isomorphic/classic/types/__tests__/ReactPropTypes-test.js +++ b/src/isomorphic/classic/types/__tests__/ReactPropTypes-test.js @@ -13,7 +13,6 @@ var PropTypes; var checkPropTypes; -var checkReactTypeSpec; var React; var ReactDOM; @@ -22,7 +21,6 @@ var MyComponent; function resetWarningCache() { jest.resetModules(); - checkReactTypeSpec = require('checkReactTypeSpec'); checkPropTypes = require('checkPropTypes'); } @@ -33,7 +31,7 @@ function getPropTypeWarningMessage(propTypes, object, componentName) { console.error.calls.reset(); } resetWarningCache(); - checkReactTypeSpec(propTypes, object, 'prop', 'testComponent'); + checkPropTypes(propTypes, object, 'prop', 'testComponent'); const callCount = console.error.calls.count(); if (callCount > 1) { throw new Error('Too many warnings.'); diff --git a/src/isomorphic/classic/types/__tests__/ReactPropTypesProduction-test.js b/src/isomorphic/classic/types/__tests__/ReactPropTypesProduction-test.js deleted file mode 100644 index 22d0ba1996477..0000000000000 --- a/src/isomorphic/classic/types/__tests__/ReactPropTypesProduction-test.js +++ /dev/null @@ -1,222 +0,0 @@ -/** - * Copyright 2013-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @emails react-core - */ - -'use strict'; - -describe('ReactPropTypesProduction', function() { - var PropTypes; - var React; - var ReactTestUtils; - var oldProcess; - - beforeEach(function() { - __DEV__ = false; - - // Mutating process.env.NODE_ENV would cause our babel plugins to do the - // wrong thing. If you change this, make sure to test with jest --no-cache. - oldProcess = process; - global.process = { - ...process, - env: {...process.env, NODE_ENV: 'production'}, - }; - - jest.resetModules(); - PropTypes = require('ReactPropTypes'); - React = require('react'); - ReactTestUtils = require('ReactTestUtils'); - }); - - afterEach(function() { - __DEV__ = true; - global.process = oldProcess; - }); - - function expectThrowsInProduction(declaration, value) { - var props = {testProp: value}; - expect(() => { - declaration(props, 'testProp', 'testComponent', 'prop'); - }).toThrowError('Minified React error #144'); - } - - describe('Primitive Types', function() { - it('should be a no-op', function() { - expectThrowsInProduction(PropTypes.array, /please/); - expectThrowsInProduction(PropTypes.array.isRequired, /please/); - expectThrowsInProduction(PropTypes.array.isRequired, null); - expectThrowsInProduction(PropTypes.array.isRequired, undefined); - expectThrowsInProduction(PropTypes.bool, []); - expectThrowsInProduction(PropTypes.bool.isRequired, []); - expectThrowsInProduction(PropTypes.bool.isRequired, null); - expectThrowsInProduction(PropTypes.bool.isRequired, undefined); - expectThrowsInProduction(PropTypes.func, false); - expectThrowsInProduction(PropTypes.func.isRequired, false); - expectThrowsInProduction(PropTypes.func.isRequired, null); - expectThrowsInProduction(PropTypes.func.isRequired, undefined); - expectThrowsInProduction(PropTypes.number, function() {}); - expectThrowsInProduction(PropTypes.number.isRequired, function() {}); - expectThrowsInProduction(PropTypes.number.isRequired, null); - expectThrowsInProduction(PropTypes.number.isRequired, undefined); - expectThrowsInProduction(PropTypes.string, 0); - expectThrowsInProduction(PropTypes.string.isRequired, 0); - expectThrowsInProduction(PropTypes.string.isRequired, null); - expectThrowsInProduction(PropTypes.string.isRequired, undefined); - expectThrowsInProduction(PropTypes.symbol, 0); - expectThrowsInProduction(PropTypes.symbol.isRequired, 0); - expectThrowsInProduction(PropTypes.symbol.isRequired, null); - expectThrowsInProduction(PropTypes.symbol.isRequired, undefined); - expectThrowsInProduction(PropTypes.object, ''); - expectThrowsInProduction(PropTypes.object.isRequired, ''); - expectThrowsInProduction(PropTypes.object.isRequired, null); - expectThrowsInProduction(PropTypes.object.isRequired, undefined); - }); - }); - - describe('Any Type', function() { - it('should be a no-op', function() { - expectThrowsInProduction(PropTypes.any, null); - expectThrowsInProduction(PropTypes.any.isRequired, null); - expectThrowsInProduction(PropTypes.any.isRequired, undefined); - }); - }); - - describe('ArrayOf Type', function() { - it('should be a no-op', function() { - expectThrowsInProduction(PropTypes.arrayOf({foo: PropTypes.string}), { - foo: 'bar', - }); - expectThrowsInProduction(PropTypes.arrayOf(PropTypes.number), [ - 1, - 2, - 'b', - ]); - expectThrowsInProduction(PropTypes.arrayOf(PropTypes.number), { - '0': 'maybe-array', - length: 1, - }); - expectThrowsInProduction( - PropTypes.arrayOf(PropTypes.number).isRequired, - null, - ); - expectThrowsInProduction( - PropTypes.arrayOf(PropTypes.number).isRequired, - undefined, - ); - }); - }); - - describe('Component Type', function() { - it('should be a no-op', function() { - expectThrowsInProduction(PropTypes.element, [
    ,
    ]); - expectThrowsInProduction(PropTypes.element, 123); - expectThrowsInProduction(PropTypes.element, 'foo'); - expectThrowsInProduction(PropTypes.element, false); - expectThrowsInProduction(PropTypes.element.isRequired, null); - expectThrowsInProduction(PropTypes.element.isRequired, undefined); - }); - }); - - describe('Instance Types', function() { - it('should be a no-op', function() { - expectThrowsInProduction(PropTypes.instanceOf(Date), {}); - expectThrowsInProduction(PropTypes.instanceOf(Date).isRequired, {}); - }); - }); - - describe('React Component Types', function() { - it('should be a no-op', function() { - expectThrowsInProduction(PropTypes.node, {}); - expectThrowsInProduction(PropTypes.node.isRequired, null); - expectThrowsInProduction(PropTypes.node.isRequired, undefined); - }); - }); - - describe('ObjectOf Type', function() { - it('should be a no-op', function() { - expectThrowsInProduction(PropTypes.objectOf({foo: PropTypes.string}), { - foo: 'bar', - }); - expectThrowsInProduction(PropTypes.objectOf(PropTypes.number), { - a: 1, - b: 2, - c: 'b', - }); - expectThrowsInProduction(PropTypes.objectOf(PropTypes.number), [1, 2]); - expectThrowsInProduction(PropTypes.objectOf(PropTypes.number), null); - expectThrowsInProduction(PropTypes.objectOf(PropTypes.number), undefined); - }); - }); - - describe('OneOf Types', function() { - it('should be a no-op', function() { - expectThrowsInProduction(PropTypes.oneOf('red', 'blue'), 'red'); - expectThrowsInProduction(PropTypes.oneOf(['red', 'blue']), true); - expectThrowsInProduction(PropTypes.oneOf(['red', 'blue']), null); - expectThrowsInProduction(PropTypes.oneOf(['red', 'blue']), undefined); - }); - }); - - describe('Union Types', function() { - it('should be a no-op', function() { - expectThrowsInProduction( - PropTypes.oneOfType(PropTypes.string, PropTypes.number), - 'red', - ); - expectThrowsInProduction( - PropTypes.oneOfType([PropTypes.string, PropTypes.number]), - [], - ); - expectThrowsInProduction( - PropTypes.oneOfType([PropTypes.string, PropTypes.number]), - null, - ); - expectThrowsInProduction( - PropTypes.oneOfType([PropTypes.string, PropTypes.number]), - undefined, - ); - }); - }); - - describe('Shape Types', function() { - it('should be a no-op', function() { - expectThrowsInProduction(PropTypes.shape({}), 'some string'); - expectThrowsInProduction( - PropTypes.shape({key: PropTypes.number}).isRequired, - null, - ); - expectThrowsInProduction( - PropTypes.shape({key: PropTypes.number}).isRequired, - undefined, - ); - }); - }); - - describe('Custom validator', function() { - beforeEach(function() { - jest.resetModules(); - }); - - it('should not have been called', function() { - var spy = jest.fn(); - var Component = React.createClass({ - propTypes: {num: spy}, - - render: function() { - return
    ; - }, - }); - - var instance = ; - ReactTestUtils.renderIntoDocument(instance); - - expect(spy).not.toBeCalled(); - }); - }); -}); diff --git a/src/isomorphic/classic/types/checkPropTypes.js b/src/isomorphic/classic/types/checkPropTypes.js index 5168efd9c001f..32d9ecc677497 100644 --- a/src/isomorphic/classic/types/checkPropTypes.js +++ b/src/isomorphic/classic/types/checkPropTypes.js @@ -11,84 +11,4 @@ 'use strict'; -var ReactPropTypesSecret = require('ReactPropTypesSecret'); - -var invariant = require('fbjs/lib/invariant'); -var warning = require('fbjs/lib/warning'); - -var loggedTypeFailures = {}; - -/** - * Assert that the values match with the type specs. - * Error messages are memorized and will only be shown once. - * - * @param {object} typeSpecs Map of name to a ReactPropType - * @param {object} values Runtime values that need to be type-checked - * @param {string} location e.g. "prop", "context", "child context" - * @param {string} componentName Name of the component for error messages. - * @param {?Function} getStack Returns the component stack. - * @private - */ -function checkPropTypes(typeSpecs, values, location, componentName, getStack) { - if (__DEV__) { - for (var typeSpecName in typeSpecs) { - if (typeSpecs.hasOwnProperty(typeSpecName)) { - var error; - // Prop type validation may throw. In case they do, we don't want to - // fail the render phase where it didn't fail before. So we log it. - // After these have been cleaned up, we'll let them throw. - try { - // This is intentionally an invariant that gets caught. It's the same - // behavior as without this statement except with a better message. - invariant( - typeof typeSpecs[typeSpecName] === 'function', - '%s: %s type `%s` is invalid; it must be a function, usually from ' + - 'React.PropTypes.', - componentName || 'React class', - location, - typeSpecName, - ); - error = typeSpecs[typeSpecName]( - values, - typeSpecName, - componentName, - location, - null, - ReactPropTypesSecret, - ); - } catch (ex) { - error = ex; - } - warning( - !error || error instanceof Error, - '%s: type specification of %s `%s` is invalid; the type checker ' + - 'function must return `null` or an `Error` but returned a %s. ' + - 'You may have forgotten to pass an argument to the type checker ' + - 'creator (arrayOf, instanceOf, objectOf, oneOf, oneOfType, and ' + - 'shape all require an argument).', - componentName || 'React class', - location, - typeSpecName, - typeof error, - ); - if (error instanceof Error && !(error.message in loggedTypeFailures)) { - // Only monitor this failure once because there tends to be a lot of the - // same error. - loggedTypeFailures[error.message] = true; - - var stack = getStack ? getStack() : ''; - - warning( - false, - 'Failed %s type: %s%s', - location, - error.message, - stack != null ? stack : '', - ); - } - } - } - } -} - -module.exports = checkPropTypes; +module.exports = require('prop-types/checkPropTypes'); diff --git a/src/umd/shims/ReactUMDShim.js b/src/isomorphic/modern/class/PropTypes.d.ts similarity index 50% rename from src/umd/shims/ReactUMDShim.js rename to src/isomorphic/modern/class/PropTypes.d.ts index 84ae35c47505b..a8802e66fe859 100644 --- a/src/umd/shims/ReactUMDShim.js +++ b/src/isomorphic/modern/class/PropTypes.d.ts @@ -1,16 +1,19 @@ -/** - * Copyright 2013-present, Facebook, Inc. +/*! + * Copyright 2015-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. - * - * @providesModule ReactUMDShim */ -/* globals React */ - -'use strict'; +/** + * TypeScript Definition File for React. + * + * Full type definitions are not yet officially supported. These are mostly + * just helpers for the unit test. + */ -module.exports = React; +declare module 'prop-types' { + export var string : any; +} diff --git a/src/isomorphic/modern/class/__tests__/ReactCoffeeScriptClass-test.coffee b/src/isomorphic/modern/class/__tests__/ReactCoffeeScriptClass-test.coffee index 1880a3dc6efaf..15f5e46eab758 100644 --- a/src/isomorphic/modern/class/__tests__/ReactCoffeeScriptClass-test.coffee +++ b/src/isomorphic/modern/class/__tests__/ReactCoffeeScriptClass-test.coffee @@ -9,6 +9,7 @@ of patent rights can be found in the PATENTS file in the same directory. React = null ReactDOM = null +PropTypes = null describe 'ReactCoffeeScriptClass', -> div = null @@ -21,6 +22,7 @@ describe 'ReactCoffeeScriptClass', -> beforeEach -> React = require 'react' ReactDOM = require 'react-dom' + PropTypes = require 'prop-types' container = document.createElement 'div' attachedListener = null renderedName = null @@ -102,8 +104,8 @@ describe 'ReactCoffeeScriptClass', -> it 'renders based on context in the constructor', -> class Foo extends React.Component @contextTypes: - tag: React.PropTypes.string - className: React.PropTypes.string + tag: PropTypes.string + className: PropTypes.string constructor: (props, context) -> super props, context @@ -118,8 +120,8 @@ describe 'ReactCoffeeScriptClass', -> class Outer extends React.Component @childContextTypes: - tag: React.PropTypes.string - className: React.PropTypes.string + tag: PropTypes.string + className: PropTypes.string getChildContext: -> tag: 'span' @@ -393,13 +395,13 @@ describe 'ReactCoffeeScriptClass', -> it 'supports this.context passed via getChildContext', -> class Bar extends React.Component @contextTypes: - bar: React.PropTypes.string + bar: PropTypes.string render: -> div className: @context.bar class Foo extends React.Component @childContextTypes: - bar: React.PropTypes.string + bar: PropTypes.string getChildContext: -> bar: 'bar-through-context' render: -> diff --git a/src/isomorphic/modern/class/__tests__/ReactES6Class-test.js b/src/isomorphic/modern/class/__tests__/ReactES6Class-test.js index a6ec313d5c105..f634e1d1c58d4 100644 --- a/src/isomorphic/modern/class/__tests__/ReactES6Class-test.js +++ b/src/isomorphic/modern/class/__tests__/ReactES6Class-test.js @@ -11,6 +11,7 @@ 'use strict'; +var PropTypes; var React; var ReactDOM; @@ -25,6 +26,7 @@ describe('ReactES6Class', () => { var renderedName = null; beforeEach(() => { + PropTypes = require('prop-types'); React = require('react'); ReactDOM = require('react-dom'); container = document.createElement('div'); @@ -123,8 +125,8 @@ describe('ReactES6Class', () => { } } Foo.contextTypes = { - tag: React.PropTypes.string, - className: React.PropTypes.string, + tag: PropTypes.string, + className: PropTypes.string, }; class Outer extends React.Component { @@ -136,8 +138,8 @@ describe('ReactES6Class', () => { } } Outer.childContextTypes = { - tag: React.PropTypes.string, - className: React.PropTypes.string, + tag: PropTypes.string, + className: PropTypes.string, }; test(, 'SPAN', 'foo'); }); @@ -419,7 +421,7 @@ describe('ReactES6Class', () => { return
    ; } } - Bar.contextTypes = {bar: React.PropTypes.string}; + Bar.contextTypes = {bar: PropTypes.string}; class Foo extends React.Component { getChildContext() { return {bar: 'bar-through-context'}; @@ -428,7 +430,7 @@ describe('ReactES6Class', () => { return ; } } - Foo.childContextTypes = {bar: React.PropTypes.string}; + Foo.childContextTypes = {bar: PropTypes.string}; test(, 'DIV', 'bar-through-context'); }); diff --git a/src/isomorphic/modern/class/__tests__/ReactTypeScriptClass-test.ts b/src/isomorphic/modern/class/__tests__/ReactTypeScriptClass-test.ts index 921474d3affbc..33085c89d9213 100644 --- a/src/isomorphic/modern/class/__tests__/ReactTypeScriptClass-test.ts +++ b/src/isomorphic/modern/class/__tests__/ReactTypeScriptClass-test.ts @@ -1,3 +1,4 @@ +/// /// /// @@ -12,6 +13,7 @@ import React = require('react'); import ReactDOM = require('react-dom'); +import PropTypes = require('prop-types'); // Before Each @@ -85,8 +87,8 @@ class StateBasedOnProps extends React.Component { // it renders based on context in the constructor class StateBasedOnContext extends React.Component { static contextTypes = { - tag: React.PropTypes.string, - className: React.PropTypes.string + tag: PropTypes.string, + className: PropTypes.string }; state = { tag: this.context.tag, @@ -100,8 +102,8 @@ class StateBasedOnContext extends React.Component { class ProvideChildContextTypes extends React.Component { static childContextTypes = { - tag: React.PropTypes.string, - className: React.PropTypes.string + tag: PropTypes.string, + className: PropTypes.string }; getChildContext() { return { tag: 'span', className: 'foo' }; @@ -278,13 +280,13 @@ class MisspelledComponent2 extends React.Component { // it supports this.context passed via getChildContext class ReadContext extends React.Component { - static contextTypes = { bar: React.PropTypes.string }; + static contextTypes = { bar: PropTypes.string }; render() { return React.createElement('div', { className: this.context.bar }); } } class ProvideContext extends React.Component { - static childContextTypes = { bar: React.PropTypes.string }; + static childContextTypes = { bar: PropTypes.string }; getChildContext() { return { bar: 'bar-through-context' }; } diff --git a/src/isomorphic/modern/element/__tests__/ReactJSXElementValidator-test.js b/src/isomorphic/modern/element/__tests__/ReactJSXElementValidator-test.js index 5074097dfa47f..ff1ff2e7c8e6d 100644 --- a/src/isomorphic/modern/element/__tests__/ReactJSXElementValidator-test.js +++ b/src/isomorphic/modern/element/__tests__/ReactJSXElementValidator-test.js @@ -17,6 +17,7 @@ var React; var ReactDOM; var ReactTestUtils; +var PropTypes; describe('ReactJSXElementValidator', () => { function normalizeCodeLocInfo(str) { @@ -29,6 +30,7 @@ describe('ReactJSXElementValidator', () => { beforeEach(() => { jest.resetModules(); + PropTypes = require('prop-types'); React = require('react'); ReactDOM = require('react-dom'); ReactTestUtils = require('ReactTestUtils'); @@ -45,7 +47,7 @@ describe('ReactJSXElementValidator', () => { } }; RequiredPropComponent.displayName = 'RequiredPropComponent'; - RequiredPropComponent.propTypes = {prop: React.PropTypes.string.isRequired}; + RequiredPropComponent.propTypes = {prop: PropTypes.string.isRequired}; }); it('warns for keys for arrays of elements in children position', () => { @@ -188,7 +190,7 @@ describe('ReactJSXElementValidator', () => { } } MyComp.propTypes = { - color: React.PropTypes.string, + color: PropTypes.string, }; class ParentComp extends React.Component { render() { @@ -211,7 +213,7 @@ describe('ReactJSXElementValidator', () => { return null; } MyComp.propTypes = { - color: React.PropTypes.string, + color: PropTypes.string, }; function MiddleComp(props) { return ; diff --git a/src/renderers/__tests__/ReactComponentLifeCycle-test.js b/src/renderers/__tests__/ReactComponentLifeCycle-test.js index e65acdfbd139c..af575d153071c 100644 --- a/src/renderers/__tests__/ReactComponentLifeCycle-test.js +++ b/src/renderers/__tests__/ReactComponentLifeCycle-test.js @@ -14,6 +14,7 @@ var React; var ReactDOM; var ReactTestUtils; +var PropTypes; var clone = function(o) { return JSON.parse(JSON.stringify(o)); @@ -91,6 +92,7 @@ describe('ReactComponentLifeCycle', () => { React = require('react'); ReactDOM = require('react-dom'); ReactTestUtils = require('ReactTestUtils'); + PropTypes = require('prop-types'); }); it('should not reuse an instance when it has been unmounted', () => { @@ -225,23 +227,28 @@ describe('ReactComponentLifeCycle', () => { it('should correctly determine if a component is mounted', () => { spyOn(console, 'error'); - var Component = React.createClass({ - componentWillMount: function() { - expect(this.isMounted()).toBeFalsy(); - }, - componentDidMount: function() { - expect(this.isMounted()).toBeTruthy(); - }, - render: function() { - expect(this.isMounted()).toBeFalsy(); + class Component extends React.Component { + _isMounted() { + // No longer a public API, but we can test that it works internally by + // reaching into the updater. + return this.updater.isMounted(this); + } + componentWillMount() { + expect(this._isMounted()).toBeFalsy(); + } + componentDidMount() { + expect(this._isMounted()).toBeTruthy(); + } + render() { + expect(this._isMounted()).toBeFalsy(); return
    ; - }, - }); + } + } var element = ; var instance = ReactTestUtils.renderIntoDocument(element); - expect(instance.isMounted()).toBeTruthy(); + expect(instance._isMounted()).toBeTruthy(); expectDev(console.error.calls.count()).toBe(1); expectDev(console.error.calls.argsFor(0)[0]).toContain( @@ -251,23 +258,28 @@ describe('ReactComponentLifeCycle', () => { it('should correctly determine if a null component is mounted', () => { spyOn(console, 'error'); - var Component = React.createClass({ - componentWillMount: function() { - expect(this.isMounted()).toBeFalsy(); - }, - componentDidMount: function() { - expect(this.isMounted()).toBeTruthy(); - }, - render: function() { - expect(this.isMounted()).toBeFalsy(); + class Component extends React.Component { + _isMounted() { + // No longer a public API, but we can test that it works internally by + // reaching into the updater. + return this.updater.isMounted(this); + } + componentWillMount() { + expect(this._isMounted()).toBeFalsy(); + } + componentDidMount() { + expect(this._isMounted()).toBeTruthy(); + } + render() { + expect(this._isMounted()).toBeFalsy(); return null; - }, - }); + } + } var element = ; var instance = ReactTestUtils.renderIntoDocument(element); - expect(instance.isMounted()).toBeTruthy(); + expect(instance._isMounted()).toBeTruthy(); expectDev(console.error.calls.count()).toBe(1); expectDev(console.error.calls.argsFor(0)[0]).toContain( @@ -276,38 +288,38 @@ describe('ReactComponentLifeCycle', () => { }); it('isMounted should return false when unmounted', () => { - var Component = React.createClass({ - render: function() { + class Component extends React.Component { + render() { return
    ; - }, - }); + } + } var container = document.createElement('div'); var instance = ReactDOM.render(, container); - expect(instance.isMounted()).toBe(true); + // No longer a public API, but we can test that it works internally by + // reaching into the updater. + expect(instance.updater.isMounted(instance)).toBe(true); ReactDOM.unmountComponentAtNode(container); - expect(instance.isMounted()).toBe(false); + expect(instance.updater.isMounted(instance)).toBe(false); }); it('warns if findDOMNode is used inside render', () => { spyOn(console, 'error'); - var Component = React.createClass({ - getInitialState: function() { - return {isMounted: false}; - }, - componentDidMount: function() { + class Component extends React.Component { + state = {isMounted: false}; + componentDidMount() { this.setState({isMounted: true}); - }, - render: function() { + } + render() { if (this.state.isMounted) { expect(ReactDOM.findDOMNode(this).tagName).toBe('DIV'); } return
    ; - }, - }); + } + } ReactTestUtils.renderIntoDocument(); expectDev(console.error.calls.count()).toBe(1); @@ -513,30 +525,31 @@ describe('ReactComponentLifeCycle', () => { return true; }; }; - var Outer = React.createClass({ - render: function() { + class Outer extends React.Component { + componentWillMount = logger('outer componentWillMount'); + componentDidMount = logger('outer componentDidMount'); + componentWillReceiveProps = logger('outer componentWillReceiveProps'); + shouldComponentUpdate = logger('outer shouldComponentUpdate'); + componentWillUpdate = logger('outer componentWillUpdate'); + componentDidUpdate = logger('outer componentDidUpdate'); + componentWillUnmount = logger('outer componentWillUnmount'); + render() { return
    ; - }, - componentWillMount: logger('outer componentWillMount'), - componentDidMount: logger('outer componentDidMount'), - componentWillReceiveProps: logger('outer componentWillReceiveProps'), - shouldComponentUpdate: logger('outer shouldComponentUpdate'), - componentWillUpdate: logger('outer componentWillUpdate'), - componentDidUpdate: logger('outer componentDidUpdate'), - componentWillUnmount: logger('outer componentWillUnmount'), - }); - var Inner = React.createClass({ - render: function() { + } + } + + class Inner extends React.Component { + componentWillMount = logger('inner componentWillMount'); + componentDidMount = logger('inner componentDidMount'); + componentWillReceiveProps = logger('inner componentWillReceiveProps'); + shouldComponentUpdate = logger('inner shouldComponentUpdate'); + componentWillUpdate = logger('inner componentWillUpdate'); + componentDidUpdate = logger('inner componentDidUpdate'); + componentWillUnmount = logger('inner componentWillUnmount'); + render() { return {this.props.x}; - }, - componentWillMount: logger('inner componentWillMount'), - componentDidMount: logger('inner componentDidMount'), - componentWillReceiveProps: logger('inner componentWillReceiveProps'), - shouldComponentUpdate: logger('inner shouldComponentUpdate'), - componentWillUpdate: logger('inner componentWillUpdate'), - componentDidUpdate: logger('inner componentDidUpdate'), - componentWillUnmount: logger('inner componentWillUnmount'), - }); + } + } var container = document.createElement('div'); log = []; @@ -594,14 +607,14 @@ describe('ReactComponentLifeCycle', () => { }; } Parent.childContextTypes = { - x: React.PropTypes.number, + x: PropTypes.number, }; function Child(props, context) { expect(context.x).toBe(2); return
    ; } Child.contextTypes = { - x: React.PropTypes.number, + x: PropTypes.number, }; const div = document.createElement('div'); diff --git a/src/renderers/__tests__/ReactComponentTreeHook-test.js b/src/renderers/__tests__/ReactComponentTreeHook-test.js index 0decdcd72fa88..1ae443f731fea 100644 --- a/src/renderers/__tests__/ReactComponentTreeHook-test.js +++ b/src/renderers/__tests__/ReactComponentTreeHook-test.js @@ -29,7 +29,7 @@ describe('ReactComponentTreeHook', () => { ReactDOM = require('react-dom'); ReactDOMServer = require('react-dom/server'); ReactInstanceMap = require('ReactInstanceMap'); - ReactComponentTreeHook = require('react/lib/ReactComponentTreeHook'); + ReactComponentTreeHook = require('ReactComponentTreeHook'); ReactComponentTreeTestUtils = require('ReactComponentTreeTestUtils'); }); @@ -41,8 +41,15 @@ describe('ReactComponentTreeHook', () => { return addendum.replace(/\(at .+?:\d+\)/g, '(at **)'); } - var Anon = React.createClass({displayName: null, render: () => null}); - var Orange = React.createClass({render: () => null}); + function Anon() { + return null; + } + Object.defineProperty(Anon, 'name', { + value: null, + }); + function Orange() { + return null; + } expectDev(getAddendum()).toBe(''); expectDev(getAddendum(
    )).toBe('\n in div (at **)'); @@ -2123,7 +2130,7 @@ describe('ReactComponentTreeHook', () => { ReactDOM = require('react-dom'); ReactDOMServer = require('react-dom/server'); ReactInstanceMap = require('ReactInstanceMap'); - ReactComponentTreeHook = require('react/lib/ReactComponentTreeHook'); + ReactComponentTreeHook = require('ReactComponentTreeHook'); ReactComponentTreeTestUtils = require('ReactComponentTreeTestUtils'); }); diff --git a/src/renderers/__tests__/ReactComponentTreeHook-test.native.js b/src/renderers/__tests__/ReactComponentTreeHook-test.native.js index 2de9bff4eff77..ff458d64a4d6d 100644 --- a/src/renderers/__tests__/ReactComponentTreeHook-test.native.js +++ b/src/renderers/__tests__/ReactComponentTreeHook-test.native.js @@ -29,14 +29,16 @@ describeStack('ReactComponentTreeHook', () => { var View; var Image; var Text; + var PropTypes; beforeEach(() => { jest.resetModules(); + PropTypes = require('prop-types'); React = require('react'); ReactNative = require('ReactNative'); ReactInstanceMap = require('ReactInstanceMap'); - ReactComponentTreeHook = require('react/lib/ReactComponentTreeHook'); + ReactComponentTreeHook = require('ReactComponentTreeHook'); ReactComponentTreeTestUtils = require('ReactComponentTreeTestUtils'); View = require('View'); createReactNativeComponentClass = require('createReactNativeComponentClass'); @@ -50,7 +52,7 @@ describeStack('ReactComponentTreeHook', () => { }); Text = class extends React.Component { static childContextTypes = { - isInAParentText: React.PropTypes.bool, + isInAParentText: PropTypes.bool, }; getChildContext() { diff --git a/src/renderers/__tests__/ReactCompositeComponent-test.js b/src/renderers/__tests__/ReactCompositeComponent-test.js index 7259ec57b2a6b..d3ead32dc5906 100644 --- a/src/renderers/__tests__/ReactCompositeComponent-test.js +++ b/src/renderers/__tests__/ReactCompositeComponent-test.js @@ -30,7 +30,7 @@ describe('ReactCompositeComponent', () => { ReactDOM = require('react-dom'); ReactDOMFeatureFlags = require('ReactDOMFeatureFlags'); ReactDOMServer = require('react-dom/server'); - ReactCurrentOwner = require('react/lib/ReactCurrentOwner'); + ReactCurrentOwner = require('ReactCurrentOwner'); ReactPropTypes = require('ReactPropTypes'); ReactTestUtils = require('ReactTestUtils'); shallowEqual = require('fbjs/lib/shallowEqual'); @@ -166,64 +166,6 @@ describe('ReactCompositeComponent', () => { expect(instance.getAnchor().className).toBe(''); }); - it('should auto bind methods and values correctly', () => { - spyOn(console, 'error'); - - var ComponentClass = React.createClass({ - getInitialState: function() { - return {valueToReturn: 'hi'}; - }, - methodToBeExplicitlyBound: function() { - return this; - }, - methodAutoBound: function() { - return this; - }, - render: function() { - return
    ; - }, - }); - var instance = ; - - // Next, prove that once mounted, the scope is bound correctly to the actual - // component. - var mountedInstance = ReactTestUtils.renderIntoDocument(instance); - - expect(function() { - mountedInstance.methodToBeExplicitlyBound.bind(instance)(); - }).not.toThrow(); - expect(function() { - mountedInstance.methodAutoBound(); - }).not.toThrow(); - - expectDev(console.error.calls.count()).toBe(1); - var explicitlyBound = mountedInstance.methodToBeExplicitlyBound.bind( - mountedInstance, - ); - expectDev(console.error.calls.count()).toBe(2); - var autoBound = mountedInstance.methodAutoBound; - - var context = {}; - expect(explicitlyBound.call(context)).toBe(mountedInstance); - expect(autoBound.call(context)).toBe(mountedInstance); - - expect(explicitlyBound.call(mountedInstance)).toBe(mountedInstance); - expect(autoBound.call(mountedInstance)).toBe(mountedInstance); - }); - - it('should not pass this to getDefaultProps', () => { - var Component = React.createClass({ - getDefaultProps: function() { - expect(this.render).not.toBeDefined(); - return {}; - }, - render: function() { - return
    ; - }, - }); - ReactTestUtils.renderIntoDocument(); - }); - it('should use default values for undefined props', () => { class Component extends React.Component { static defaultProps = {prop: 'testKey'}; @@ -1204,17 +1146,17 @@ describe('ReactCompositeComponent', () => { }); it('should replace state', () => { - var Moo = React.createClass({ - getInitialState: function() { - return {x: 1}; - }, - render: function() { + class Moo extends React.Component { + state = {x: 1}; + render() { return
    ; - }, - }); + } + } var moo = ReactTestUtils.renderIntoDocument(); - moo.replaceState({y: 2}); + // No longer a public API, but we can test that it works internally by + // reaching into the updater. + moo.updater.enqueueReplaceState(moo, {y: 2}); expect('x' in moo.state).toBe(false); expect(moo.state.y).toBe(2); }); @@ -1226,21 +1168,22 @@ describe('ReactCompositeComponent', () => { NotActuallyImmutable.prototype.amIImmutable = function() { return true; }; - var Moo = React.createClass({ - getInitialState: function() { - return new NotActuallyImmutable('first'); - }, - render: function() { + class Moo extends React.Component { + state = new NotActuallyImmutable('first'); + // No longer a public API, but we can test that it works internally by + // reaching into the updater. + _replaceState = update => this.updater.enqueueReplaceState(this, update); + render() { return
    ; - }, - }); + } + } var moo = ReactTestUtils.renderIntoDocument(); expect(moo.state.str).toBe('first'); expect(moo.state.amIImmutable()).toBe(true); var secondState = new NotActuallyImmutable('second'); - moo.replaceState(secondState); + moo._replaceState(secondState); expect(moo.state.str).toBe('second'); expect(moo.state.amIImmutable()).toBe(true); expect(moo.state).toBe(secondState); @@ -1254,14 +1197,14 @@ describe('ReactCompositeComponent', () => { var fifthState = new NotActuallyImmutable('fifth'); ReactDOM.unstable_batchedUpdates(function() { moo.setState({str: 'fourth'}); - moo.replaceState(fifthState); + moo._replaceState(fifthState); }); expect(moo.state).toBe(fifthState); // When more than one state update is enqueued, we have the same behavior var sixthState = new NotActuallyImmutable('sixth'); ReactDOM.unstable_batchedUpdates(function() { - moo.replaceState(sixthState); + moo._replaceState(sixthState); moo.setState({str: 'seventh'}); }); expect(moo.state.str).toBe('seventh'); diff --git a/src/renderers/__tests__/ReactCompositeComponentState-test.js b/src/renderers/__tests__/ReactCompositeComponentState-test.js index 6bed124cfd919..958d40692daac 100644 --- a/src/renderers/__tests__/ReactCompositeComponentState-test.js +++ b/src/renderers/__tests__/ReactCompositeComponentState-test.js @@ -24,34 +24,34 @@ describe('ReactCompositeComponent-state', () => { ReactDOM = require('react-dom'); ReactDOMFeatureFlags = require('ReactDOMFeatureFlags'); - TestComponent = React.createClass({ - peekAtState: function(from, state) { - state = state || this.state; - this.props.stateListener(from, state && state.color); - }, + TestComponent = class extends React.Component { + constructor(props) { + super(props); + this.peekAtState('getInitialState', undefined, props); + this.state = {color: 'red'}; + } - peekAtCallback: function(from) { + peekAtState = (from, state = this.state, props = this.props) => { + props.stateListener(from, state && state.color); + }; + + peekAtCallback = from => { return () => this.peekAtState(from); - }, + }; - setFavoriteColor: function(nextColor) { + setFavoriteColor(nextColor) { this.setState( {color: nextColor}, this.peekAtCallback('setFavoriteColor'), ); - }, - - getInitialState: function() { - this.peekAtState('getInitialState'); - return {color: 'red'}; - }, + } - render: function() { + render() { this.peekAtState('render'); return
    {this.state.color}
    ; - }, + } - componentWillMount: function() { + componentWillMount() { this.peekAtState('componentWillMount-start'); this.setState(function(state) { this.peekAtState('before-setState-sunrise', state); @@ -72,25 +72,27 @@ describe('ReactCompositeComponent-state', () => { this.peekAtState('after-setState-orange', state); }); this.peekAtState('componentWillMount-end'); - }, + } - componentDidMount: function() { + componentDidMount() { this.peekAtState('componentDidMount-start'); this.setState( {color: 'yellow'}, this.peekAtCallback('setState-yellow'), ); this.peekAtState('componentDidMount-end'); - }, + } - componentWillReceiveProps: function(newProps) { + componentWillReceiveProps(newProps) { this.peekAtState('componentWillReceiveProps-start'); if (newProps.nextColor) { this.setState(function(state) { this.peekAtState('before-setState-receiveProps', state); return {color: newProps.nextColor}; }); - this.replaceState({color: undefined}); + // No longer a public API, but we can test that it works internally by + // reaching into the updater. + this.updater.enqueueReplaceState(this, {color: undefined}); this.setState( function(state) { this.peekAtState('before-setState-again-receiveProps', state); @@ -103,28 +105,28 @@ describe('ReactCompositeComponent-state', () => { }); } this.peekAtState('componentWillReceiveProps-end'); - }, + } - shouldComponentUpdate: function(nextProps, nextState) { + shouldComponentUpdate(nextProps, nextState) { this.peekAtState('shouldComponentUpdate-currentState'); this.peekAtState('shouldComponentUpdate-nextState', nextState); return true; - }, + } - componentWillUpdate: function(nextProps, nextState) { + componentWillUpdate(nextProps, nextState) { this.peekAtState('componentWillUpdate-currentState'); this.peekAtState('componentWillUpdate-nextState', nextState); - }, + } - componentDidUpdate: function(prevProps, prevState) { + componentDidUpdate(prevProps, prevState) { this.peekAtState('componentDidUpdate-currentState'); this.peekAtState('componentDidUpdate-prevState', prevState); - }, + } - componentWillUnmount: function() { + componentWillUnmount() { this.peekAtState('componentWillUnmount'); - }, - }); + } + }; }); it('should support setting state', () => { diff --git a/src/renderers/__tests__/ReactErrorBoundaries-test.js b/src/renderers/__tests__/ReactErrorBoundaries-test.js index d9a34dc5e6f84..49e9a1273dc28 100644 --- a/src/renderers/__tests__/ReactErrorBoundaries-test.js +++ b/src/renderers/__tests__/ReactErrorBoundaries-test.js @@ -13,6 +13,7 @@ var ReactDOMFeatureFlags = require('ReactDOMFeatureFlags'); +var PropTypes; var React; var ReactDOM; @@ -37,6 +38,7 @@ describe('ReactErrorBoundaries', () => { var Normal; beforeEach(() => { + PropTypes = require('prop-types'); ReactDOM = require('react-dom'); React = require('react'); @@ -737,7 +739,7 @@ describe('ReactErrorBoundaries', () => { it('renders an error state if context provider throws in componentWillMount', () => { class BrokenComponentWillMountWithContext extends React.Component { - static childContextTypes = {foo: React.PropTypes.number}; + static childContextTypes = {foo: PropTypes.number}; getChildContext() { return {foo: 42}; } @@ -774,7 +776,7 @@ describe('ReactErrorBoundaries', () => { }; } BrokenComponentWillMountWithContext.childContextTypes = { - foo: React.PropTypes.number, + foo: PropTypes.number, }; var container = document.createElement('div'); diff --git a/src/renderers/__tests__/ReactHostOperationHistoryHook-test.js b/src/renderers/__tests__/ReactHostOperationHistoryHook-test.js index b787fa13f575d..81fc2683eb0c8 100644 --- a/src/renderers/__tests__/ReactHostOperationHistoryHook-test.js +++ b/src/renderers/__tests__/ReactHostOperationHistoryHook-test.js @@ -27,7 +27,7 @@ describeStack('ReactHostOperationHistoryHook', () => { jest.resetModules(); React = require('react'); - ReactPerf = require('react-dom/lib/ReactPerf'); + ReactPerf = require('ReactPerf'); ReactDOM = require('react-dom'); ReactDOMComponentTree = require('ReactDOMComponentTree'); ReactHostOperationHistoryHook = require('ReactHostOperationHistoryHook'); diff --git a/src/renderers/__tests__/ReactMultiChild-test.js b/src/renderers/__tests__/ReactMultiChild-test.js index 2a78651a4dcd7..d3c408f7ac203 100644 --- a/src/renderers/__tests__/ReactMultiChild-test.js +++ b/src/renderers/__tests__/ReactMultiChild-test.js @@ -35,14 +35,14 @@ describe('ReactMultiChild', () => { var mockUpdate = jest.fn(); var mockUnmount = jest.fn(); - var MockComponent = React.createClass({ - componentDidMount: mockMount, - componentDidUpdate: mockUpdate, - componentWillUnmount: mockUnmount, - render: function() { + class MockComponent extends React.Component { + componentDidMount = mockMount; + componentDidUpdate = mockUpdate; + componentWillUnmount = mockUnmount; + render() { return ; - }, - }); + } + } expect(mockMount.mock.calls.length).toBe(0); expect(mockUpdate.mock.calls.length).toBe(0); @@ -67,13 +67,13 @@ describe('ReactMultiChild', () => { var mockMount = jest.fn(); var mockUnmount = jest.fn(); - var MockComponent = React.createClass({ - componentDidMount: mockMount, - componentWillUnmount: mockUnmount, - render: function() { + class MockComponent extends React.Component { + componentDidMount = mockMount; + componentWillUnmount = mockUnmount; + render() { return ; - }, - }); + } + } expect(mockMount.mock.calls.length).toBe(0); expect(mockUnmount.mock.calls.length).toBe(0); @@ -95,13 +95,13 @@ describe('ReactMultiChild', () => { var mockMount = jest.fn(); var mockUnmount = jest.fn(); - var MockComponent = React.createClass({ - componentDidMount: mockMount, - componentWillUnmount: mockUnmount, - render: function() { + class MockComponent extends React.Component { + componentDidMount = mockMount; + componentWillUnmount = mockUnmount; + render() { return ; - }, - }); + } + } class WrapperComponent extends React.Component { render() { @@ -132,13 +132,13 @@ describe('ReactMultiChild', () => { var mockMount = jest.fn(); var mockUnmount = jest.fn(); - var MockComponent = React.createClass({ - componentDidMount: mockMount, - componentWillUnmount: mockUnmount, - render: function() { + class MockComponent extends React.Component { + componentDidMount = mockMount; + componentWillUnmount = mockUnmount; + render() { return ; - }, - }); + } + } expect(mockMount.mock.calls.length).toBe(0); expect(mockUnmount.mock.calls.length).toBe(0); diff --git a/src/renderers/__tests__/ReactPerf-test.js b/src/renderers/__tests__/ReactPerf-test.js index 7957266e6d70c..a610e9523d497 100644 --- a/src/renderers/__tests__/ReactPerf-test.js +++ b/src/renderers/__tests__/ReactPerf-test.js @@ -41,7 +41,7 @@ describeStack('ReactPerf', () => { React = require('react'); ReactDOM = require('react-dom'); - ReactPerf = require('react-dom/lib/ReactPerf'); + ReactPerf = require('ReactPerf'); ReactTestUtils = require('ReactTestUtils'); emptyFunction = require('fbjs/lib/emptyFunction'); @@ -64,16 +64,16 @@ describeStack('ReactPerf', () => { } }; - LifeCycle = React.createClass({ - shouldComponentUpdate: emptyFunction.thatReturnsTrue, - componentWillMount: emptyFunction, - componentDidMount: emptyFunction, - componentWillReceiveProps: emptyFunction, - componentWillUpdate: emptyFunction, - componentDidUpdate: emptyFunction, - componentWillUnmount: emptyFunction, - render: emptyFunction.thatReturnsNull, - }); + LifeCycle = class extends React.Component { + shouldComponentUpdate = emptyFunction.thatReturnsTrue; + componentWillMount = emptyFunction; + componentDidMount = emptyFunction; + componentWillReceiveProps = emptyFunction; + componentWillUpdate = emptyFunction; + componentDidUpdate = emptyFunction; + componentWillUnmount = emptyFunction; + render = emptyFunction.thatReturnsNull; + }; }); afterEach(() => { diff --git a/src/renderers/__tests__/ReactStatelessComponent-test.js b/src/renderers/__tests__/ReactStatelessComponent-test.js index b3cacfbf09ec6..0792eded59464 100644 --- a/src/renderers/__tests__/ReactStatelessComponent-test.js +++ b/src/renderers/__tests__/ReactStatelessComponent-test.js @@ -11,6 +11,7 @@ 'use strict'; +var PropTypes; var React; var ReactDOM; var ReactTestUtils; @@ -28,6 +29,7 @@ describe('ReactStatelessComponent', () => { beforeEach(() => { jest.resetModuleRegistry(); + PropTypes = require('prop-types'); React = require('react'); ReactDOM = require('react-dom'); ReactTestUtils = require('ReactTestUtils'); @@ -68,7 +70,7 @@ describe('ReactStatelessComponent', () => { it('should pass context thru stateless component', () => { class Child extends React.Component { static contextTypes = { - test: React.PropTypes.string.isRequired, + test: PropTypes.string.isRequired, }; render() { @@ -82,7 +84,7 @@ describe('ReactStatelessComponent', () => { class GrandParent extends React.Component { static childContextTypes = { - test: React.PropTypes.string.isRequired, + test: PropTypes.string.isRequired, }; getChildContext() { @@ -111,7 +113,7 @@ describe('ReactStatelessComponent', () => { } StatelessComponentWithChildContext.childContextTypes = { - foo: React.PropTypes.string, + foo: PropTypes.string, }; var container = document.createElement('div'); @@ -249,15 +251,14 @@ describe('ReactStatelessComponent', () => { it('deduplicates ref warnings based on element or owner', () => { spyOn(console, 'error'); - // Prevent the Babel transform adding a displayName. - var createClassWithoutDisplayName = React.createClass; - // When owner uses JSX, we can use exact line location to dedupe warnings - var AnonymousParentUsingJSX = createClassWithoutDisplayName({ + class AnonymousParentUsingJSX extends React.Component { render() { return {}} />; - }, - }); + } + } + Object.defineProperty(AnonymousParentUsingJSX, 'name', {value: undefined}); + const instance1 = ReactTestUtils.renderIntoDocument( , ); @@ -273,14 +274,18 @@ describe('ReactStatelessComponent', () => { console.error.calls.reset(); // When owner doesn't use JSX, and is anonymous, we warn once per internal instance. - var AnonymousParentNotUsingJSX = createClassWithoutDisplayName({ + class AnonymousParentNotUsingJSX extends React.Component { render() { return React.createElement(StatelessComponent, { name: 'A', ref: () => {}, }); - }, + } + } + Object.defineProperty(AnonymousParentNotUsingJSX, 'name', { + value: undefined, }); + const instance2 = ReactTestUtils.renderIntoDocument( , ); @@ -352,7 +357,7 @@ describe('ReactStatelessComponent', () => { return
    {props.test}
    ; } Child.defaultProps = {test: 2}; - Child.propTypes = {test: React.PropTypes.string}; + Child.propTypes = {test: PropTypes.string}; spyOn(console, 'error'); ReactTestUtils.renderIntoDocument(); @@ -369,7 +374,7 @@ describe('ReactStatelessComponent', () => { it('should receive context', () => { class Parent extends React.Component { static childContextTypes = { - lang: React.PropTypes.string, + lang: PropTypes.string, }; getChildContext() { @@ -384,7 +389,7 @@ describe('ReactStatelessComponent', () => { function Child(props, context) { return
    {context.lang}
    ; } - Child.contextTypes = {lang: React.PropTypes.string}; + Child.contextTypes = {lang: PropTypes.string}; var el = document.createElement('div'); ReactDOM.render(, el); diff --git a/src/renderers/__tests__/ReactUpdates-test.js b/src/renderers/__tests__/ReactUpdates-test.js index 982881b52181e..bba996cbc65c4 100644 --- a/src/renderers/__tests__/ReactUpdates-test.js +++ b/src/renderers/__tests__/ReactUpdates-test.js @@ -391,30 +391,23 @@ describe('ReactUpdates', () => { }, }; - var Box = React.createClass({ - mixins: [UpdateLoggingMixin], - - render: function() { + class Box extends React.Component { + render() { return
    {this.props.children}
    ; - }, - }); - - var Child = React.createClass({ - mixins: [UpdateLoggingMixin], + } + } + Object.assign(Box.prototype, UpdateLoggingMixin); - render: function() { + class Child extends React.Component { + render() { return child; - }, - }); - - var Switcher = React.createClass({ - mixins: [UpdateLoggingMixin], - - getInitialState: function() { - return {tabKey: 'hello'}; - }, + } + } + Object.assign(Child.prototype, UpdateLoggingMixin); - render: function() { + class Switcher extends React.Component { + state = {tabKey: 'hello'}; + render() { var child = this.props.children; return ( @@ -428,20 +421,20 @@ describe('ReactUpdates', () => {
    ); - }, - }); - - var App = React.createClass({ - mixins: [UpdateLoggingMixin], + } + } + Object.assign(Switcher.prototype, UpdateLoggingMixin); - render: function() { + class App extends React.Component { + render() { return ( ); - }, - }); + } + } + Object.assign(App.prototype, UpdateLoggingMixin); var root = ; root = ReactTestUtils.renderIntoDocument(root); @@ -913,52 +906,6 @@ describe('ReactUpdates', () => { expect(console.error.calls.count()).toBe(3); }); - it('throws in replaceState if the update callback is not a function', () => { - spyOn(console, 'error'); - - function Foo() { - this.a = 1; - this.b = 2; - } - var A = React.createClass({ - getInitialState: function() { - return {}; - }, - render: function() { - return
    ; - }, - }); - var component = ReactTestUtils.renderIntoDocument(); - - expect(() => component.replaceState({}, 'no')).toThrowError( - 'Invalid argument passed as callback. Expected a function. Instead ' + - 'received: no', - ); - expectDev(console.error.calls.argsFor(0)[0]).toContain( - 'replaceState(...): Expected the last optional `callback` argument to be ' + - 'a function. Instead received: no.', - ); - component = ReactTestUtils.renderIntoDocument(); - expect(() => component.replaceState({}, {foo: 'bar'})).toThrowError( - 'Invalid argument passed as callback. Expected a function. Instead ' + - 'received: [object Object]', - ); - expectDev(console.error.calls.argsFor(1)[0]).toContain( - 'replaceState(...): Expected the last optional `callback` argument to be ' + - 'a function. Instead received: [object Object].', - ); - component = ReactTestUtils.renderIntoDocument(); - expect(() => component.replaceState({}, new Foo())).toThrowError( - 'Invalid argument passed as callback. Expected a function. Instead ' + - 'received: [object Object]', - ); - expectDev(console.error.calls.argsFor(2)[0]).toContain( - 'replaceState(...): Expected the last optional `callback` argument to be ' + - 'a function. Instead received: [object Object].', - ); - expect(console.error.calls.count()).toBe(3); - }); - it('throws in forceUpdate if the update callback is not a function', () => { spyOn(console, 'error'); diff --git a/src/renderers/__tests__/refs-test.js b/src/renderers/__tests__/refs-test.js index 758fb51af04bf..202a101f1c8dc 100644 --- a/src/renderers/__tests__/refs-test.js +++ b/src/renderers/__tests__/refs-test.js @@ -317,7 +317,7 @@ describe('string refs between fiber and stack', () => { it('attaches, detaches from fiber component with stack layer', () => { spyOn(console, 'error'); - const ReactCurrentOwner = require('react/lib/ReactCurrentOwner'); + const ReactCurrentOwner = require('ReactCurrentOwner'); const ReactDOM = require('react-dom'); const ReactDOMFiber = require('ReactDOMFiber'); const ReactInstanceMap = require('ReactInstanceMap'); @@ -364,7 +364,7 @@ describe('string refs between fiber and stack', () => { it('attaches, detaches from stack component with fiber layer', () => { spyOn(console, 'error'); - const ReactCurrentOwner = require('react/lib/ReactCurrentOwner'); + const ReactCurrentOwner = require('ReactCurrentOwner'); const ReactDOM = require('react-dom'); const ReactDOMFiber = require('ReactDOMFiber'); const ReactInstanceMap = require('ReactInstanceMap'); diff --git a/src/renderers/art/ReactARTStack.js b/src/renderers/art/ReactARTStack.js index db1d22a885e86..26e3ff92e82f6 100644 --- a/src/renderers/art/ReactARTStack.js +++ b/src/renderers/art/ReactARTStack.js @@ -170,12 +170,8 @@ const ContainerMixin = Object.assign({}, ReactMultiChild, { // Surface is a React DOM Component, not an ART component. It serves as the // entry point into the ART reconciler. -const Surface = React.createClass({ - displayName: 'Surface', - - mixins: [ContainerMixin], - - componentDidMount: function() { +class Surface extends React.Component { + componentDidMount() { const domNode = ReactDOM.findDOMNode(this); this.node = Mode.Surface(+this.props.width, +this.props.height, domNode); @@ -189,9 +185,9 @@ const Surface = React.createClass({ ReactInstanceMap.get(this)._context, ); ReactUpdates.ReactReconcileTransaction.release(transaction); - }, + } - componentDidUpdate: function(oldProps) { + componentDidUpdate(oldProps) { const node = this.node; if ( this.props.width != oldProps.width || this.props.height != oldProps.height @@ -220,13 +216,13 @@ const Surface = React.createClass({ if (node.render) { node.render(); } - }, + } - componentWillUnmount: function() { + componentWillUnmount() { this.unmountChildren(); - }, + } - render: function() { + render() { // This is going to be a placeholder because we don't know what it will // actually resolve to because ART may render canvas, vml or svg tags here. // We only allow a subset of properties since others might conflict with @@ -246,8 +242,10 @@ const Surface = React.createClass({ title={props.title} /> ); - }, -}); + } +} +Surface.displayName = 'Surface'; +Object.assign(Surface.prototype, ContainerMixin); // Various nodes that can go into a surface diff --git a/src/renderers/dom/ReactDOM.js b/src/renderers/dom/ReactDOM.js index 23a189f0c5c43..f5f976ea7d4aa 100644 --- a/src/renderers/dom/ReactDOM.js +++ b/src/renderers/dom/ReactDOM.js @@ -38,6 +38,11 @@ var ReactDOM = { unstable_batchedUpdates: ReactGenericBatching.batchedUpdates, unstable_renderSubtreeIntoContainer: ReactMount.renderSubtreeIntoContainer, /* eslint-enable camelcase */ + + __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: { + // For TapEventPlugin which is popular in open source + EventPluginHub: require('EventPluginHub'), + }, }; // Inject the runtime into a devtools global hook regardless of browser. diff --git a/src/renderers/dom/fiber/ReactDOMFiber.js b/src/renderers/dom/fiber/ReactDOMFiber.js index be4d800a7dfaa..bb327bb36b441 100644 --- a/src/renderers/dom/fiber/ReactDOMFiber.js +++ b/src/renderers/dom/fiber/ReactDOMFiber.js @@ -30,6 +30,11 @@ var ReactInstanceMap = require('ReactInstanceMap'); var ReactPortal = require('ReactPortal'); var {isValidElement} = require('react'); var {injectInternals} = require('ReactFiberDevToolsHook'); +var { + ELEMENT_NODE, + DOCUMENT_NODE, + DOCUMENT_FRAGMENT_NODE, +} = require('HTMLNodeType'); var findDOMNode = require('findDOMNode'); var invariant = require('fbjs/lib/invariant'); @@ -52,8 +57,6 @@ if (__DEV__) { var {updatedAncestorInfo} = validateDOMNesting; } -const DOCUMENT_NODE = 9; - ReactDOMInjection.inject(); ReactControlledComponent.injection.injectFiberControlledHostComponent( ReactDOMFiberComponent, @@ -83,10 +86,6 @@ type HostContext = HostContextDev | HostContextProd; let eventsEnabled: ?boolean = null; let selectionInformation: ?mixed = null; -var ELEMENT_NODE_TYPE = 1; -var DOC_NODE_TYPE = 9; -var DOCUMENT_FRAGMENT_NODE_TYPE = 11; - /** * True if the supplied DOM node is a valid node element. * @@ -96,9 +95,9 @@ var DOCUMENT_FRAGMENT_NODE_TYPE = 11; */ function isValidContainer(node) { return !!(node && - (node.nodeType === ELEMENT_NODE_TYPE || - node.nodeType === DOC_NODE_TYPE || - node.nodeType === DOCUMENT_FRAGMENT_NODE_TYPE)); + (node.nodeType === ELEMENT_NODE || + node.nodeType === DOCUMENT_NODE || + node.nodeType === DOCUMENT_FRAGMENT_NODE)); } function validateContainer(container) { @@ -112,7 +111,7 @@ function getReactRootElementInContainer(container: any) { return null; } - if (container.nodeType === DOC_NODE_TYPE) { + if (container.nodeType === DOCUMENT_NODE) { return container.documentElement; } else { return container.firstChild; @@ -539,6 +538,11 @@ var ReactDOM = { unstable_batchedUpdates: ReactGenericBatching.batchedUpdates, unstable_deferredUpdates: DOMRenderer.deferredUpdates, + + __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: { + // For TapEventPlugin which is popular in open source + EventPluginHub: require('EventPluginHub'), + }, }; if (typeof injectInternals === 'function') { diff --git a/src/renderers/dom/fiber/ReactDOMFiberComponent.js b/src/renderers/dom/fiber/ReactDOMFiberComponent.js index 37933932a9c6e..a0a2597beedb6 100644 --- a/src/renderers/dom/fiber/ReactDOMFiberComponent.js +++ b/src/renderers/dom/fiber/ReactDOMFiberComponent.js @@ -25,6 +25,7 @@ var ReactDOMFiberOption = require('ReactDOMFiberOption'); var ReactDOMFiberSelect = require('ReactDOMFiberSelect'); var ReactDOMFiberTextarea = require('ReactDOMFiberTextarea'); var {getCurrentFiberOwnerName} = require('ReactDebugCurrentFiber'); +var {DOCUMENT_FRAGMENT_NODE} = require('HTMLNodeType'); var emptyFunction = require('fbjs/lib/emptyFunction'); var invariant = require('fbjs/lib/invariant'); @@ -63,9 +64,6 @@ var { mathml: MATH_NAMESPACE, } = DOMNamespaces; -// Node type for document fragments (Node.DOCUMENT_FRAGMENT_NODE). -var DOC_FRAGMENT_TYPE = 11; - function getDeclarationErrorAddendum() { if (__DEV__) { var ownerName = getCurrentFiberOwnerName(); @@ -144,7 +142,7 @@ if (__DEV__) { } function getDocument(rootContainerElement) { - var isDocumentFragment = rootContainerElement.nodeType === DOC_FRAGMENT_TYPE; + var isDocumentFragment = rootContainerElement.nodeType === DOCUMENT_FRAGMENT_NODE; var doc = isDocumentFragment ? rootContainerElement : rootContainerElement.ownerDocument; @@ -345,10 +343,13 @@ var ReactDOMFiberComponent = { if (namespaceURI === HTML_NAMESPACE) { namespaceURI = getIntrinsicNamespace(type); } + if (__DEV__) { + var isCustomComponentTag = isCustomComponent(type, props); + } if (namespaceURI === HTML_NAMESPACE) { if (__DEV__) { warning( - type === type.toLowerCase() || isCustomComponent(type, props), + isCustomComponentTag || type === type.toLowerCase(), '<%s /> is using uppercase HTML. Always use lowercase HTML tags ' + 'in React.', type, @@ -375,6 +376,20 @@ var ReactDOMFiberComponent = { domElement = ownerDocument.createElementNS(namespaceURI, type); } + if (__DEV__) { + if (namespaceURI === HTML_NAMESPACE) { + warning( + isCustomComponentTag || + Object.prototype.toString.call(domElement) !== + '[object HTMLUnknownElement]', + 'The tag <%s> is unrecognized in this browser. ' + + 'If you meant to render a React component, start its name with ' + + 'an uppercase letter.', + type, + ); + } + } + return domElement; }, diff --git a/src/renderers/dom/fiber/__tests__/ReactDOMFiber-test.js b/src/renderers/dom/fiber/__tests__/ReactDOMFiber-test.js index a2280be28f1f2..026fa910355a5 100644 --- a/src/renderers/dom/fiber/__tests__/ReactDOMFiber-test.js +++ b/src/renderers/dom/fiber/__tests__/ReactDOMFiber-test.js @@ -15,6 +15,7 @@ var React = require('react'); var ReactDOM = require('react-dom'); var ReactDOMFeatureFlags = require('ReactDOMFeatureFlags'); var ReactTestUtils = require('ReactTestUtils'); +var PropTypes = require('prop-types'); describe('ReactDOMFiber', () => { var container; @@ -683,7 +684,7 @@ describe('ReactDOMFiber', () => { class Component extends React.Component { static contextTypes = { - foo: React.PropTypes.string.isRequired, + foo: PropTypes.string.isRequired, }; render() { @@ -693,7 +694,7 @@ describe('ReactDOMFiber', () => { class Parent extends React.Component { static childContextTypes = { - foo: React.PropTypes.string.isRequired, + foo: PropTypes.string.isRequired, }; getChildContext() { @@ -717,8 +718,8 @@ describe('ReactDOMFiber', () => { class Component extends React.Component { static contextTypes = { - foo: React.PropTypes.string.isRequired, - getFoo: React.PropTypes.func.isRequired, + foo: PropTypes.string.isRequired, + getFoo: PropTypes.func.isRequired, }; render() { @@ -728,8 +729,8 @@ describe('ReactDOMFiber', () => { class Parent extends React.Component { static childContextTypes = { - foo: React.PropTypes.string.isRequired, - getFoo: React.PropTypes.func.isRequired, + foo: PropTypes.string.isRequired, + getFoo: PropTypes.func.isRequired, }; state = { @@ -761,8 +762,8 @@ describe('ReactDOMFiber', () => { class Component extends React.Component { static contextTypes = { - foo: React.PropTypes.string.isRequired, - getFoo: React.PropTypes.func.isRequired, + foo: PropTypes.string.isRequired, + getFoo: PropTypes.func.isRequired, }; render() { @@ -772,8 +773,8 @@ describe('ReactDOMFiber', () => { class Parent extends React.Component { static childContextTypes = { - foo: React.PropTypes.string.isRequired, - getFoo: React.PropTypes.func.isRequired, + foo: PropTypes.string.isRequired, + getFoo: PropTypes.func.isRequired, }; getChildContext() { diff --git a/src/renderers/dom/fiber/wrappers/ReactDOMFiberInput.js b/src/renderers/dom/fiber/wrappers/ReactDOMFiberInput.js index f0bbc0adcaef3..ebc9812077ea2 100644 --- a/src/renderers/dom/fiber/wrappers/ReactDOMFiberInput.js +++ b/src/renderers/dom/fiber/wrappers/ReactDOMFiberInput.js @@ -25,6 +25,10 @@ var ReactControlledValuePropTypes = require('ReactControlledValuePropTypes'); var ReactDOMComponentTree = require('ReactDOMComponentTree'); var {getCurrentFiberOwnerName} = require('ReactDebugCurrentFiber'); +if (__DEV__) { + var {getCurrentFiberStackAddendum} = require('ReactDebugCurrentFiber'); +} + var invariant = require('fbjs/lib/invariant'); var warning = require('fbjs/lib/warning'); @@ -90,7 +94,7 @@ var ReactDOMInput = { ReactControlledValuePropTypes.checkPropTypes( 'input', props, - getCurrentFiberOwnerName(), + getCurrentFiberStackAddendum, ); if ( @@ -154,12 +158,12 @@ var ReactDOMInput = { ) { warning( false, - '%s is changing an uncontrolled input of type %s to be controlled. ' + + 'A component is changing an uncontrolled input of type %s to be controlled. ' + 'Input elements should not switch from uncontrolled to controlled (or vice versa). ' + 'Decide between using a controlled or uncontrolled input ' + - 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components', - getCurrentFiberOwnerName() || 'A component', + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components%s', props.type, + getCurrentFiberStackAddendum(), ); didWarnUncontrolledToControlled = true; } @@ -170,12 +174,12 @@ var ReactDOMInput = { ) { warning( false, - '%s is changing a controlled input of type %s to be uncontrolled. ' + + 'A component is changing a controlled input of type %s to be uncontrolled. ' + 'Input elements should not switch from controlled to uncontrolled (or vice versa). ' + 'Decide between using a controlled or uncontrolled input ' + - 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components', - getCurrentFiberOwnerName() || 'A component', + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components%s', props.type, + getCurrentFiberStackAddendum(), ); didWarnControlledToUncontrolled = true; } diff --git a/src/renderers/dom/fiber/wrappers/ReactDOMFiberSelect.js b/src/renderers/dom/fiber/wrappers/ReactDOMFiberSelect.js index 2ceb3114b1bf7..4659ec7f01350 100644 --- a/src/renderers/dom/fiber/wrappers/ReactDOMFiberSelect.js +++ b/src/renderers/dom/fiber/wrappers/ReactDOMFiberSelect.js @@ -23,6 +23,10 @@ var ReactControlledValuePropTypes = require('ReactControlledValuePropTypes'); var {getCurrentFiberOwnerName} = require('ReactDebugCurrentFiber'); var warning = require('fbjs/lib/warning'); +if (__DEV__) { + var {getCurrentFiberStackAddendum} = require('ReactDebugCurrentFiber'); +} + var didWarnValueDefaultValue = false; function getDeclarationErrorAddendum() { @@ -42,7 +46,7 @@ function checkSelectPropTypes(props) { ReactControlledValuePropTypes.checkPropTypes( 'select', props, - getCurrentFiberOwnerName(), + getCurrentFiberStackAddendum, ); for (var i = 0; i < valuePropNames.length; i++) { diff --git a/src/renderers/dom/fiber/wrappers/ReactDOMFiberTextarea.js b/src/renderers/dom/fiber/wrappers/ReactDOMFiberTextarea.js index 43c91a362372a..2f99352a0aaa1 100644 --- a/src/renderers/dom/fiber/wrappers/ReactDOMFiberTextarea.js +++ b/src/renderers/dom/fiber/wrappers/ReactDOMFiberTextarea.js @@ -19,11 +19,14 @@ type TextAreaWithWrapperState = HTMLTextAreaElement & { }; var ReactControlledValuePropTypes = require('ReactControlledValuePropTypes'); -var {getCurrentFiberOwnerName} = require('ReactDebugCurrentFiber'); var invariant = require('fbjs/lib/invariant'); var warning = require('fbjs/lib/warning'); +if (__DEV__) { + var {getCurrentFiberStackAddendum} = require('ReactDebugCurrentFiber'); +} + var didWarnValDefaultVal = false; /** @@ -69,7 +72,7 @@ var ReactDOMTextarea = { ReactControlledValuePropTypes.checkPropTypes( 'textarea', props, - getCurrentFiberOwnerName(), + getCurrentFiberStackAddendum, ); if ( props.value !== undefined && diff --git a/src/umd/shims/ReactCurrentOwnerUMDShim.js b/src/renderers/dom/shared/HTMLNodeType.js similarity index 55% rename from src/umd/shims/ReactCurrentOwnerUMDShim.js rename to src/renderers/dom/shared/HTMLNodeType.js index 79ef232b54b07..35566be173211 100644 --- a/src/umd/shims/ReactCurrentOwnerUMDShim.js +++ b/src/renderers/dom/shared/HTMLNodeType.js @@ -6,13 +6,21 @@ * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. * - * @providesModule ReactCurrentOwnerUMDShim + * @providesModule HTMLNodeType */ -/* globals React */ - 'use strict'; -var ReactInternals = React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED; +/** + * HTML nodeType values that represent the type of the node + */ + +var HTMLNodeType = { + ELEMENT_NODE: 1, + TEXT_NODE: 3, + COMMENT_NODE: 8, + DOCUMENT_NODE: 9, + DOCUMENT_FRAGMENT_NODE: 11, +}; -module.exports = ReactInternals.ReactCurrentOwner; +module.exports = HTMLNodeType; diff --git a/src/renderers/dom/shared/ReactDOMComponentTree.js b/src/renderers/dom/shared/ReactDOMComponentTree.js index 600188f0951a3..978d60d51ec8d 100644 --- a/src/renderers/dom/shared/ReactDOMComponentTree.js +++ b/src/renderers/dom/shared/ReactDOMComponentTree.js @@ -14,6 +14,7 @@ var DOMProperty = require('DOMProperty'); var ReactDOMComponentFlags = require('ReactDOMComponentFlags'); var {HostComponent, HostText} = require('ReactTypeOfWork'); +var {ELEMENT_NODE, COMMENT_NODE} = require('HTMLNodeType'); var invariant = require('fbjs/lib/invariant'); @@ -30,11 +31,12 @@ var internalEventHandlersKey = '__reactEventHandlers$' + randomKey; * Check if a given node should be cached. */ function shouldPrecacheNode(node, nodeID) { - return (node.nodeType === 1 && + return (node.nodeType === ELEMENT_NODE && node.getAttribute(ATTR_NAME) === '' + nodeID) || - (node.nodeType === 8 && + (node.nodeType === COMMENT_NODE && node.nodeValue === ' react-text: ' + nodeID + ' ') || - (node.nodeType === 8 && node.nodeValue === ' react-empty: ' + nodeID + ' '); + (node.nodeType === COMMENT_NODE && + node.nodeValue === ' react-empty: ' + nodeID + ' '); } /** diff --git a/src/renderers/dom/shared/ReactInputSelection.js b/src/renderers/dom/shared/ReactInputSelection.js index dae4085f6a78a..2fd94d4ef0e05 100644 --- a/src/renderers/dom/shared/ReactInputSelection.js +++ b/src/renderers/dom/shared/ReactInputSelection.js @@ -12,6 +12,7 @@ 'use strict'; var ReactDOMSelection = require('ReactDOMSelection'); +var {ELEMENT_NODE} = require('HTMLNodeType'); var containsNode = require('fbjs/lib/containsNode'); var focusNode = require('fbjs/lib/focusNode'); @@ -64,7 +65,7 @@ var ReactInputSelection = { const ancestors = []; let ancestor = priorFocusedElem; while ((ancestor = ancestor.parentNode)) { - if (ancestor.nodeType === 1) { + if (ancestor.nodeType === ELEMENT_NODE) { ancestors.push({ element: ancestor, left: ancestor.scrollLeft, diff --git a/src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js b/src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js index 7a8c73ddc79e7..ca2ec1cf0079b 100644 --- a/src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js +++ b/src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js @@ -809,6 +809,32 @@ describe('ReactDOMComponent', () => { ); }); + it('should warn if the tag is unrecognized', () => { + if (ReactDOMFeatureFlags.useCreateElement) { + spyOn(console, 'error'); + + let realToString; + try { + realToString = Object.prototype.toString; + Object.prototype.toString = function() { + // Emulate browser behavior which is missing in jsdom + if (this instanceof window.HTMLUnknownElement) { + return '[object HTMLUnknownElement]'; + } + return realToString.apply(this, arguments); + }; + ReactTestUtils.renderIntoDocument(); + } finally { + Object.prototype.toString = realToString; + } + + expectDev(console.error.calls.count()).toBe(1); + expectDev(console.error.calls.argsFor(0)[0]).toContain( + 'The tag is unrecognized in this browser', + ); + } + }); + it('should warn against children for void elements', () => { var container = document.createElement('div'); @@ -868,11 +894,11 @@ describe('ReactDOMComponent', () => { container.shadyRoot = {}; return container; }; - var ShadyComponent = React.createClass({ + class ShadyComponent extends React.Component { render() { return ; - }, - }); + } + } var node = document.createElement('div'); ReactDOM.render(, node); expectDev(console.error.calls.count()).toBe(1); @@ -917,6 +943,8 @@ describe('ReactDOMComponent', () => { }); it('should treat menuitem as a void element but still create the closing tag', () => { + // menuitem is not implemented in jsdom, so this triggers the unknown warning error + spyOn(console, 'error'); var container = document.createElement('div'); var returnedValue = ReactDOMServer.renderToString( @@ -1350,12 +1378,12 @@ describe('ReactDOMComponent', () => { it('gives useful context in warnings', () => { spyOn(console, 'error'); - var Row = React.createClass({ - render: () => , - }); - var FancyRow = React.createClass({ - render: () => , - }); + function Row() { + return ; + } + function FancyRow() { + return ; + } class Table extends React.Component { render() { @@ -1369,12 +1397,12 @@ describe('ReactDOMComponent', () => { } } - var Viz1 = React.createClass({ - render: () =>
    , - }); - var App1 = React.createClass({ - render: () => , - }); + function Viz1() { + return
    ; + } + function App1() { + return ; + } ReactTestUtils.renderIntoDocument(); expectDev(console.error.calls.count()).toBe(1); expectDev( @@ -1389,12 +1417,12 @@ describe('ReactDOMComponent', () => { : 'See Viz1 > table > FancyRow > Row > tr.', ); - var Viz2 = React.createClass({ - render: () => , - }); - var App2 = React.createClass({ - render: () => , - }); + function Viz2() { + return ; + } + function App2() { + return ; + } ReactTestUtils.renderIntoDocument(); expectDev(console.error.calls.count()).toBe(2); expectDev( diff --git a/src/renderers/dom/shared/__tests__/ReactDOMServerIntegration-test.js b/src/renderers/dom/shared/__tests__/ReactDOMServerIntegration-test.js index af29b8d5ac882..0381ec8fb82aa 100644 --- a/src/renderers/dom/shared/__tests__/ReactDOMServerIntegration-test.js +++ b/src/renderers/dom/shared/__tests__/ReactDOMServerIntegration-test.js @@ -12,6 +12,7 @@ 'use strict'; let ExecutionEnvironment; +let PropTypes; let React; let ReactDOM; let ReactDOMServer; @@ -206,6 +207,7 @@ function expectMarkupMismatch(serverElement, clientElement) { // To get around this, we must reload React modules in between server and client render. function resetModules() { jest.resetModuleRegistry(); + PropTypes = require('prop-types'); React = require('React'); ReactDOM = require('ReactDOM'); ReactDOMServer = require('ReactDOMServer'); @@ -1585,7 +1587,7 @@ describe('ReactDOMServerIntegration', () => { return this.props.children; } } - Parent.childContextTypes = {text: React.PropTypes.string}; + Parent.childContextTypes = {text: PropTypes.string}; PurpleContext = props => {props.children}; RedContext = props => {props.children}; @@ -1597,7 +1599,7 @@ describe('ReactDOMServerIntegration', () => { return
    {this.context.text}
    ; } } - ClassChildWithContext.contextTypes = {text: React.PropTypes.string}; + ClassChildWithContext.contextTypes = {text: PropTypes.string}; const e = await render( , @@ -1609,7 +1611,7 @@ describe('ReactDOMServerIntegration', () => { function StatelessChildWithContext(props, context) { return
    {context.text}
    ; } - StatelessChildWithContext.contextTypes = {text: React.PropTypes.string}; + StatelessChildWithContext.contextTypes = {text: PropTypes.string}; const e = await render( , @@ -1650,7 +1652,7 @@ describe('ReactDOMServerIntegration', () => { return
    {this.context.text}
    ; } } - ClassChildWithWrongContext.contextTypes = {foo: React.PropTypes.string}; + ClassChildWithWrongContext.contextTypes = {foo: PropTypes.string}; const e = await render( , @@ -1664,7 +1666,7 @@ describe('ReactDOMServerIntegration', () => { return
    {context.text}
    ; } StatelessChildWithWrongContext.contextTypes = { - foo: React.PropTypes.string, + foo: PropTypes.string, }; const e = await render( @@ -1677,7 +1679,7 @@ describe('ReactDOMServerIntegration', () => { function Grandchild(props, context) { return
    {context.text}
    ; } - Grandchild.contextTypes = {text: React.PropTypes.string}; + Grandchild.contextTypes = {text: PropTypes.string}; const Child = props => ; @@ -1689,7 +1691,7 @@ describe('ReactDOMServerIntegration', () => { const Grandchild = (props, context) => { return
    {context.text}
    ; }; - Grandchild.contextTypes = {text: React.PropTypes.string}; + Grandchild.contextTypes = {text: PropTypes.string}; const e = await render( , @@ -1706,7 +1708,7 @@ describe('ReactDOMServerIntegration', () => { return ; } } - Parent.childContextTypes = {text1: React.PropTypes.string}; + Parent.childContextTypes = {text1: PropTypes.string}; class Child extends React.Component { getChildContext() { @@ -1716,7 +1718,7 @@ describe('ReactDOMServerIntegration', () => { return ; } } - Child.childContextTypes = {text2: React.PropTypes.string}; + Child.childContextTypes = {text2: PropTypes.string}; const Grandchild = (props, context) => { return ( @@ -1727,8 +1729,8 @@ describe('ReactDOMServerIntegration', () => { ); }; Grandchild.contextTypes = { - text1: React.PropTypes.string, - text2: React.PropTypes.string, + text1: PropTypes.string, + text2: PropTypes.string, }; const e = await render(); @@ -1750,12 +1752,12 @@ describe('ReactDOMServerIntegration', () => { return ; } } - WillMountContext.childContextTypes = {text: React.PropTypes.string}; + WillMountContext.childContextTypes = {text: PropTypes.string}; const Child = (props, context) => { return
    {context.text}
    ; }; - Child.contextTypes = {text: React.PropTypes.string}; + Child.contextTypes = {text: PropTypes.string}; const e = await render(); expect(e.textContent).toBe('foo'); @@ -1788,7 +1790,7 @@ describe('ReactDOMServerIntegration', () => { return {value1: 'foo', value2: 'bar'}; } } - Component.childContextTypes = {value1: React.PropTypes.string}; + Component.childContextTypes = {value1: PropTypes.string}; return render(); }, ); diff --git a/src/renderers/dom/shared/__tests__/ReactMount-test.js b/src/renderers/dom/shared/__tests__/ReactMount-test.js index 3f49f1845657c..fb942d15b8aec 100644 --- a/src/renderers/dom/shared/__tests__/ReactMount-test.js +++ b/src/renderers/dom/shared/__tests__/ReactMount-test.js @@ -89,13 +89,13 @@ describe('ReactMount', () => { var mockMount = jest.fn(); var mockUnmount = jest.fn(); - var Component = React.createClass({ - componentDidMount: mockMount, - componentWillUnmount: mockUnmount, - render: function() { + class Component extends React.Component { + componentDidMount = mockMount; + componentWillUnmount = mockUnmount; + render() { return {this.props.text}; - }, - }); + } + } expect(mockMount.mock.calls.length).toBe(0); expect(mockUnmount.mock.calls.length).toBe(0); diff --git a/src/renderers/dom/shared/__tests__/ReactServerRendering-test.js b/src/renderers/dom/shared/__tests__/ReactServerRendering-test.js index 93f291c2b0783..889356a1f135b 100644 --- a/src/renderers/dom/shared/__tests__/ReactServerRendering-test.js +++ b/src/renderers/dom/shared/__tests__/ReactServerRendering-test.js @@ -490,32 +490,6 @@ describe('ReactDOMServer', () => { expect(markup).toBe('
    hello
    '); }); - it('warns with a no-op when an async replaceState is triggered', () => { - var Bar = React.createClass({ - componentWillMount: function() { - this.replaceState({text: 'hello'}); - setTimeout(() => { - this.replaceState({text: 'error'}); - }); - }, - render: function() { - return
    {}}>{this.state.text}
    ; - }, - }); - - spyOn(console, 'error'); - ReactDOMServer.renderToString(); - jest.runOnlyPendingTimers(); - expectDev(console.error.calls.count()).toBe(1); - expectDev(console.error.calls.mostRecent().args[0]).toBe( - 'Warning: replaceState(...): Can only update a mounting component. ' + - 'This usually means you called replaceState() outside componentWillMount() on the server. ' + - 'This is a no-op.\n\nPlease check the code for the Bar component.', - ); - var markup = ReactDOMServer.renderToStaticMarkup(); - expect(markup).toBe('
    hello
    '); - }); - it('warns with a no-op when an async forceUpdate is triggered', () => { class Baz extends React.Component { componentWillMount() { diff --git a/src/renderers/dom/shared/__tests__/renderSubtreeIntoContainer-test.js b/src/renderers/dom/shared/__tests__/renderSubtreeIntoContainer-test.js index 30e617480b1fa..a0a30dfe8d726 100644 --- a/src/renderers/dom/shared/__tests__/renderSubtreeIntoContainer-test.js +++ b/src/renderers/dom/shared/__tests__/renderSubtreeIntoContainer-test.js @@ -12,6 +12,7 @@ 'use strict'; var React = require('react'); +var PropTypes = require('prop-types'); var ReactDOM = require('react-dom'); var ReactTestUtils = require('ReactTestUtils'); var renderSubtreeIntoContainer = require('renderSubtreeIntoContainer'); @@ -22,7 +23,7 @@ describe('renderSubtreeIntoContainer', () => { class Component extends React.Component { static contextTypes = { - foo: React.PropTypes.string.isRequired, + foo: PropTypes.string.isRequired, }; render() { @@ -32,7 +33,7 @@ describe('renderSubtreeIntoContainer', () => { class Parent extends React.Component { static childContextTypes = { - foo: React.PropTypes.string.isRequired, + foo: PropTypes.string.isRequired, }; getChildContext() { @@ -63,7 +64,7 @@ describe('renderSubtreeIntoContainer', () => { class Component extends React.Component { static contextTypes = { - foo: React.PropTypes.string.isRequired, + foo: PropTypes.string.isRequired, }; render() { @@ -76,7 +77,7 @@ describe('renderSubtreeIntoContainer', () => { // eslint-disable-next-line no-unused-vars class Parent extends React.Component { static childContextTypes = { - foo: React.PropTypes.string.isRequired, + foo: PropTypes.string.isRequired, }; getChildContext() { @@ -104,8 +105,8 @@ describe('renderSubtreeIntoContainer', () => { class Component extends React.Component { static contextTypes = { - foo: React.PropTypes.string.isRequired, - getFoo: React.PropTypes.func.isRequired, + foo: PropTypes.string.isRequired, + getFoo: PropTypes.func.isRequired, }; render() { @@ -115,8 +116,8 @@ describe('renderSubtreeIntoContainer', () => { class Parent extends React.Component { static childContextTypes = { - foo: React.PropTypes.string.isRequired, - getFoo: React.PropTypes.func.isRequired, + foo: PropTypes.string.isRequired, + getFoo: PropTypes.func.isRequired, }; state = { @@ -156,8 +157,8 @@ describe('renderSubtreeIntoContainer', () => { class Component extends React.Component { static contextTypes = { - foo: React.PropTypes.string.isRequired, - getFoo: React.PropTypes.func.isRequired, + foo: PropTypes.string.isRequired, + getFoo: PropTypes.func.isRequired, }; render() { @@ -167,8 +168,8 @@ describe('renderSubtreeIntoContainer', () => { class Parent extends React.Component { static childContextTypes = { - foo: React.PropTypes.string.isRequired, - getFoo: React.PropTypes.func.isRequired, + foo: PropTypes.string.isRequired, + getFoo: PropTypes.func.isRequired, }; getChildContext() { @@ -229,7 +230,7 @@ describe('renderSubtreeIntoContainer', () => { return {value: this.props.value}; } static childContextTypes = { - value: React.PropTypes.string.isRequired, + value: PropTypes.string.isRequired, }; } @@ -244,7 +245,7 @@ describe('renderSubtreeIntoContainer', () => { class Child extends React.Component { static contextTypes = { - value: React.PropTypes.string.isRequired, + value: PropTypes.string.isRequired, }; render() { return
    {this.context.value}
    ; @@ -272,7 +273,7 @@ describe('renderSubtreeIntoContainer', () => { renderSubtreeIntoContainer(this, , portal1); } static childContextTypes = { - value: React.PropTypes.string.isRequired, + value: PropTypes.string.isRequired, }; } @@ -287,7 +288,7 @@ describe('renderSubtreeIntoContainer', () => { class Child extends React.Component { static contextTypes = { - value: React.PropTypes.string.isRequired, + value: PropTypes.string.isRequired, }; render() { return
    {this.context.value}
    ; diff --git a/src/renderers/dom/shared/eventPlugins/SelectEventPlugin.js b/src/renderers/dom/shared/eventPlugins/SelectEventPlugin.js index 1989139b1a0e5..c075529e21c5e 100644 --- a/src/renderers/dom/shared/eventPlugins/SelectEventPlugin.js +++ b/src/renderers/dom/shared/eventPlugins/SelectEventPlugin.js @@ -17,6 +17,7 @@ var ReactBrowserEventEmitter = require('ReactBrowserEventEmitter'); var ReactDOMComponentTree = require('ReactDOMComponentTree'); var ReactInputSelection = require('ReactInputSelection'); var SyntheticEvent = require('SyntheticEvent'); +var {DOCUMENT_NODE} = require('HTMLNodeType'); var getActiveElement = require('fbjs/lib/getActiveElement'); var isTextInputElement = require('isTextInputElement'); @@ -148,7 +149,7 @@ var SelectEventPlugin = { ) { var doc = nativeEventTarget.window === nativeEventTarget ? nativeEventTarget.document - : nativeEventTarget.nodeType === 9 + : nativeEventTarget.nodeType === DOCUMENT_NODE ? nativeEventTarget : nativeEventTarget.ownerDocument; if (!doc || !isListeningToAllDependencies('onSelect', doc)) { diff --git a/src/renderers/dom/shared/eventPlugins/__tests__/BeforeInputEventPlugin-test.js b/src/renderers/dom/shared/eventPlugins/__tests__/BeforeInputEventPlugin-test.js index cc99a00dbca33..5007116a6ea09 100644 --- a/src/renderers/dom/shared/eventPlugins/__tests__/BeforeInputEventPlugin-test.js +++ b/src/renderers/dom/shared/eventPlugins/__tests__/BeforeInputEventPlugin-test.js @@ -207,9 +207,11 @@ describe('BeforeInputEventPlugin', function() { function TestEditableReactComponent(Emulator, Scenario, ExpectedResult) { ModuleCache = new initialize(Emulator); - var EditableDiv = React.createClass({ - render: () =>
    , - }); + class EditableDiv extends React.Component { + render() { + return
    ; + } + } var rendered = ReactTestUtils.renderIntoDocument(); var node = ModuleCache.ReactDOM.findDOMNode(rendered); diff --git a/src/renderers/dom/shared/findDOMNode.js b/src/renderers/dom/shared/findDOMNode.js index ea1ef8d0f423d..1374e320a6b31 100644 --- a/src/renderers/dom/shared/findDOMNode.js +++ b/src/renderers/dom/shared/findDOMNode.js @@ -10,8 +10,9 @@ * @flow */ -var ReactCurrentOwner = require('react/lib/ReactCurrentOwner'); var ReactInstanceMap = require('ReactInstanceMap'); +var {ELEMENT_NODE} = require('HTMLNodeType'); +var {ReactCurrentOwner} = require('ReactGlobalSharedState'); var getComponentName = require('getComponentName'); var invariant = require('fbjs/lib/invariant'); @@ -53,7 +54,7 @@ const findDOMNode = function( if (componentOrElement == null) { return null; } - if ((componentOrElement: any).nodeType === 1) { + if ((componentOrElement: any).nodeType === ELEMENT_NODE) { return (componentOrElement: any); } diff --git a/src/renderers/dom/shared/hooks/ReactDOMInvalidARIAHook.js b/src/renderers/dom/shared/hooks/ReactDOMInvalidARIAHook.js index fb89d76220a66..6ada3db767a02 100644 --- a/src/renderers/dom/shared/hooks/ReactDOMInvalidARIAHook.js +++ b/src/renderers/dom/shared/hooks/ReactDOMInvalidARIAHook.js @@ -20,9 +20,10 @@ var warnedProperties = {}; var rARIA = new RegExp('^(aria)-[' + DOMProperty.ATTRIBUTE_NAME_CHAR + ']*$'); if (__DEV__) { + var {ReactComponentTreeHook} = require('ReactGlobalSharedState'); var { getStackAddendumByID, - } = require('react/lib/ReactComponentTreeHook'); + } = ReactComponentTreeHook; } function getStackAddendum(debugID) { diff --git a/src/renderers/dom/shared/hooks/ReactDOMNullInputValuePropHook.js b/src/renderers/dom/shared/hooks/ReactDOMNullInputValuePropHook.js index 92e2cf2c197d4..6b722b48641af 100644 --- a/src/renderers/dom/shared/hooks/ReactDOMNullInputValuePropHook.js +++ b/src/renderers/dom/shared/hooks/ReactDOMNullInputValuePropHook.js @@ -15,9 +15,8 @@ var ReactDebugCurrentFiber = require('ReactDebugCurrentFiber'); var warning = require('fbjs/lib/warning'); if (__DEV__) { - var { - getStackAddendumByID, - } = require('react/lib/ReactComponentTreeHook'); + var {ReactComponentTreeHook} = require('ReactGlobalSharedState'); + var {getStackAddendumByID} = ReactComponentTreeHook; } var didWarnValueNull = false; diff --git a/src/renderers/dom/shared/hooks/ReactDOMUnknownPropertyHook.js b/src/renderers/dom/shared/hooks/ReactDOMUnknownPropertyHook.js index c603e4ca76865..86b28f92e24c0 100644 --- a/src/renderers/dom/shared/hooks/ReactDOMUnknownPropertyHook.js +++ b/src/renderers/dom/shared/hooks/ReactDOMUnknownPropertyHook.js @@ -13,8 +13,8 @@ var DOMProperty = require('DOMProperty'); var EventPluginRegistry = require('EventPluginRegistry'); -var ReactComponentTreeHook = require('react/lib/ReactComponentTreeHook'); var ReactDebugCurrentFiber = require('ReactDebugCurrentFiber'); +var {ReactComponentTreeHook} = require('ReactGlobalSharedState'); var warning = require('fbjs/lib/warning'); diff --git a/src/renderers/dom/shared/setTextContent.js b/src/renderers/dom/shared/setTextContent.js index 64db36c40b40d..cef8fbc78e947 100644 --- a/src/renderers/dom/shared/setTextContent.js +++ b/src/renderers/dom/shared/setTextContent.js @@ -14,6 +14,7 @@ var ExecutionEnvironment = require('fbjs/lib/ExecutionEnvironment'); var escapeTextContentForBrowser = require('escapeTextContentForBrowser'); var setInnerHTML = require('setInnerHTML'); +var {TEXT_NODE} = require('HTMLNodeType'); /** * Set the textContent property of a node, ensuring that whitespace is preserved @@ -30,7 +31,9 @@ var setTextContent = function(node, text) { var firstChild = node.firstChild; if ( - firstChild && firstChild === node.lastChild && firstChild.nodeType === 3 + firstChild && + firstChild === node.lastChild && + firstChild.nodeType === TEXT_NODE ) { firstChild.nodeValue = text; return; @@ -42,7 +45,7 @@ var setTextContent = function(node, text) { if (ExecutionEnvironment.canUseDOM) { if (!('textContent' in document.documentElement)) { setTextContent = function(node, text) { - if (node.nodeType === 3) { + if (node.nodeType === TEXT_NODE) { node.nodeValue = text; return; } diff --git a/src/renderers/dom/shared/utils/ReactControlledValuePropTypes.js b/src/renderers/dom/shared/utils/ReactControlledValuePropTypes.js index e0cdedf22f5c7..48c37b67a3039 100644 --- a/src/renderers/dom/shared/utils/ReactControlledValuePropTypes.js +++ b/src/renderers/dom/shared/utils/ReactControlledValuePropTypes.js @@ -11,7 +11,7 @@ 'use strict'; -var React = require('react'); +var PropTypes = require('prop-types'); var ReactPropTypesSecret = require('ReactPropTypesSecret'); var warning = require('fbjs/lib/warning'); @@ -57,23 +57,17 @@ var propTypes = { 'set either `onChange` or `readOnly`.', ); }, - onChange: React.PropTypes.func, + onChange: PropTypes.func, }; var loggedTypeFailures = {}; -function getDeclarationErrorAddendum(ownerName) { - if (ownerName) { - return '\n\nCheck the render method of `' + ownerName + '`.'; - } - return ''; -} /** * Provide a linked `value` attribute for controlled forms. You should not use * this outside of the ReactDOM controlled form components. */ var ReactControlledValuePropTypes = { - checkPropTypes: function(tagName, props, ownerName) { + checkPropTypes: function(tagName, props, getStack) { for (var propName in propTypes) { if (propTypes.hasOwnProperty(propName)) { var error = propTypes[propName]( @@ -90,8 +84,7 @@ var ReactControlledValuePropTypes = { // same error. loggedTypeFailures[error.message] = true; - var addendum = getDeclarationErrorAddendum(ownerName); - warning(false, 'Failed form propType: %s%s', error.message, addendum); + warning(false, 'Failed form propType: %s%s', error.message, getStack()); } } }, diff --git a/src/renderers/dom/shared/utils/getEventTarget.js b/src/renderers/dom/shared/utils/getEventTarget.js index 83bd08b2a2558..bcd806aec48c2 100644 --- a/src/renderers/dom/shared/utils/getEventTarget.js +++ b/src/renderers/dom/shared/utils/getEventTarget.js @@ -11,6 +11,8 @@ 'use strict'; +var {TEXT_NODE} = require('HTMLNodeType'); + /** * Gets the target node from a native browser event by accounting for * inconsistencies in browser DOM APIs. @@ -28,7 +30,7 @@ function getEventTarget(nativeEvent) { // Safari may fire events on text nodes (Node.TEXT_NODE is 3). // @see http://www.quirksmode.org/js/events_properties.html - return target.nodeType === 3 ? target.parentNode : target; + return target.nodeType === TEXT_NODE ? target.parentNode : target; } module.exports = getEventTarget; diff --git a/src/renderers/dom/shared/utils/getNodeForCharacterOffset.js b/src/renderers/dom/shared/utils/getNodeForCharacterOffset.js index 65d3a33a91b6d..4a300293f91a9 100644 --- a/src/renderers/dom/shared/utils/getNodeForCharacterOffset.js +++ b/src/renderers/dom/shared/utils/getNodeForCharacterOffset.js @@ -11,6 +11,8 @@ 'use strict'; +var {TEXT_NODE} = require('HTMLNodeType'); + /** * Given any node return the first leaf node without children. * @@ -53,7 +55,7 @@ function getNodeForCharacterOffset(root, offset) { var nodeEnd = 0; while (node) { - if (node.nodeType === 3) { + if (node.nodeType === TEXT_NODE) { nodeEnd = nodeStart + node.textContent.length; if (nodeStart <= offset && nodeEnd >= offset) { diff --git a/src/renderers/dom/shared/wrappers/__tests__/ReactDOMInput-test.js b/src/renderers/dom/shared/wrappers/__tests__/ReactDOMInput-test.js index 62df49a453262..ac76902e0cf15 100644 --- a/src/renderers/dom/shared/wrappers/__tests__/ReactDOMInput-test.js +++ b/src/renderers/dom/shared/wrappers/__tests__/ReactDOMInput-test.js @@ -21,6 +21,10 @@ describe('ReactDOMInput', () => { var ReactTestUtils; var inputValueTracking; + function normalizeCodeLocInfo(str) { + return str && str.replace(/\(at .+?:\d+\)/g, '(at **)'); + } + function setUntrackedValue(elem, value) { var tracker = inputValueTracking._getTrackerFromNode(elem); var current = tracker.getValue(); @@ -638,6 +642,13 @@ describe('ReactDOMInput', () => { , ); expectDev(console.error.calls.count()).toBe(1); + expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( + 'Warning: Failed form propType: You provided a `value` prop to a form ' + + 'field without an `onChange` handler. This will render a read-only ' + + 'field. If the field should be mutable use `defaultValue`. ' + + 'Otherwise, set either `onChange` or `readOnly`.\n' + + ' in input (at **)', + ); }); it('should have a this value of undefined if bind is not used', () => { @@ -738,11 +749,12 @@ describe('ReactDOMInput', () => { ReactDOM.render(stub, container); ReactDOM.render(, container); expectDev(console.error.calls.count()).toBe(1); - expectDev(console.error.calls.argsFor(0)[0]).toContain( - 'A component is changing a controlled input of type text to be uncontrolled. ' + + expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( + 'Warning: A component is changing a controlled input of type text to be uncontrolled. ' + 'Input elements should not switch from controlled to uncontrolled (or vice versa). ' + 'Decide between using a controlled or uncontrolled input ' + - 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components', + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' + + ' in input (at **)', ); }); @@ -754,11 +766,12 @@ describe('ReactDOMInput', () => { ReactDOM.render(stub, container); ReactDOM.render(, container); expectDev(console.error.calls.count()).toBeGreaterThan(0); - expectDev(console.error.calls.argsFor(1)[0]).toContain( - 'A component is changing a controlled input of type text to be uncontrolled. ' + + expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toBe( + 'Warning: A component is changing a controlled input of type text to be uncontrolled. ' + 'Input elements should not switch from controlled to uncontrolled (or vice versa). ' + 'Decide between using a controlled or uncontrolled input ' + - 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components', + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' + + ' in input (at **)', ); }); @@ -773,11 +786,12 @@ describe('ReactDOMInput', () => { container, ); expectDev(console.error.calls.count()).toBe(1); - expectDev(console.error.calls.argsFor(0)[0]).toContain( - 'A component is changing a controlled input of type text to be uncontrolled. ' + + expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( + 'Warning: A component is changing a controlled input of type text to be uncontrolled. ' + 'Input elements should not switch from controlled to uncontrolled (or vice versa). ' + 'Decide between using a controlled or uncontrolled input ' + - 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components', + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' + + ' in input (at **)', ); }); @@ -787,11 +801,12 @@ describe('ReactDOMInput', () => { ReactDOM.render(stub, container); ReactDOM.render(, container); expectDev(console.error.calls.count()).toBe(1); - expectDev(console.error.calls.argsFor(0)[0]).toContain( - 'A component is changing an uncontrolled input of type text to be controlled. ' + + expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( + 'Warning: A component is changing an uncontrolled input of type text to be controlled. ' + 'Input elements should not switch from uncontrolled to controlled (or vice versa). ' + 'Decide between using a controlled or uncontrolled input ' + - 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components', + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' + + ' in input (at **)', ); }); @@ -801,11 +816,12 @@ describe('ReactDOMInput', () => { ReactDOM.render(stub, container); ReactDOM.render(, container); expectDev(console.error.calls.count()).toBeGreaterThan(0); - expectDev(console.error.calls.argsFor(1)[0]).toContain( - 'A component is changing an uncontrolled input of type text to be controlled. ' + + expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toBe( + 'Warning: A component is changing an uncontrolled input of type text to be controlled. ' + 'Input elements should not switch from uncontrolled to controlled (or vice versa). ' + 'Decide between using a controlled or uncontrolled input ' + - 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components', + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' + + ' in input (at **)', ); }); @@ -817,11 +833,12 @@ describe('ReactDOMInput', () => { ReactDOM.render(stub, container); ReactDOM.render(, container); expectDev(console.error.calls.count()).toBe(1); - expectDev(console.error.calls.argsFor(0)[0]).toContain( - 'A component is changing a controlled input of type checkbox to be uncontrolled. ' + + expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( + 'Warning: A component is changing a controlled input of type checkbox to be uncontrolled. ' + 'Input elements should not switch from controlled to uncontrolled (or vice versa). ' + 'Decide between using a controlled or uncontrolled input ' + - 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components', + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' + + ' in input (at **)', ); }); @@ -833,11 +850,12 @@ describe('ReactDOMInput', () => { ReactDOM.render(stub, container); ReactDOM.render(, container); expectDev(console.error.calls.count()).toBe(1); - expectDev(console.error.calls.argsFor(0)[0]).toContain( - 'A component is changing a controlled input of type checkbox to be uncontrolled. ' + + expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( + 'Warning: A component is changing a controlled input of type checkbox to be uncontrolled. ' + 'Input elements should not switch from controlled to uncontrolled (or vice versa). ' + 'Decide between using a controlled or uncontrolled input ' + - 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components', + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' + + ' in input (at **)', ); }); @@ -849,11 +867,12 @@ describe('ReactDOMInput', () => { ReactDOM.render(stub, container); ReactDOM.render(, container); expectDev(console.error.calls.count()).toBe(1); - expectDev(console.error.calls.argsFor(0)[0]).toContain( - 'A component is changing a controlled input of type checkbox to be uncontrolled. ' + + expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( + 'Warning: A component is changing a controlled input of type checkbox to be uncontrolled. ' + 'Input elements should not switch from controlled to uncontrolled (or vice versa). ' + 'Decide between using a controlled or uncontrolled input ' + - 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components', + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' + + ' in input (at **)', ); }); @@ -863,11 +882,12 @@ describe('ReactDOMInput', () => { ReactDOM.render(stub, container); ReactDOM.render(, container); expectDev(console.error.calls.count()).toBe(1); - expectDev(console.error.calls.argsFor(0)[0]).toContain( - 'A component is changing an uncontrolled input of type checkbox to be controlled. ' + + expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( + 'Warning: A component is changing an uncontrolled input of type checkbox to be controlled. ' + 'Input elements should not switch from uncontrolled to controlled (or vice versa). ' + 'Decide between using a controlled or uncontrolled input ' + - 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components', + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' + + ' in input (at **)', ); }); @@ -877,11 +897,12 @@ describe('ReactDOMInput', () => { ReactDOM.render(stub, container); ReactDOM.render(, container); expectDev(console.error.calls.count()).toBe(1); - expectDev(console.error.calls.argsFor(0)[0]).toContain( - 'A component is changing an uncontrolled input of type checkbox to be controlled. ' + + expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( + 'Warning: A component is changing an uncontrolled input of type checkbox to be controlled. ' + 'Input elements should not switch from uncontrolled to controlled (or vice versa). ' + 'Decide between using a controlled or uncontrolled input ' + - 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components', + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' + + ' in input (at **)', ); }); @@ -891,11 +912,12 @@ describe('ReactDOMInput', () => { ReactDOM.render(stub, container); ReactDOM.render(, container); expectDev(console.error.calls.count()).toBe(1); - expectDev(console.error.calls.argsFor(0)[0]).toContain( - 'A component is changing a controlled input of type radio to be uncontrolled. ' + + expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( + 'Warning: A component is changing a controlled input of type radio to be uncontrolled. ' + 'Input elements should not switch from controlled to uncontrolled (or vice versa). ' + 'Decide between using a controlled or uncontrolled input ' + - 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components', + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' + + ' in input (at **)', ); }); @@ -905,11 +927,12 @@ describe('ReactDOMInput', () => { ReactDOM.render(stub, container); ReactDOM.render(, container); expectDev(console.error.calls.count()).toBe(1); - expectDev(console.error.calls.argsFor(0)[0]).toContain( - 'A component is changing a controlled input of type radio to be uncontrolled. ' + + expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( + 'Warning: A component is changing a controlled input of type radio to be uncontrolled. ' + 'Input elements should not switch from controlled to uncontrolled (or vice versa). ' + 'Decide between using a controlled or uncontrolled input ' + - 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components', + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' + + ' in input (at **)', ); }); @@ -919,11 +942,12 @@ describe('ReactDOMInput', () => { ReactDOM.render(stub, container); ReactDOM.render(, container); expectDev(console.error.calls.count()).toBe(1); - expectDev(console.error.calls.argsFor(0)[0]).toContain( - 'A component is changing a controlled input of type radio to be uncontrolled. ' + + expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( + 'Warning: A component is changing a controlled input of type radio to be uncontrolled. ' + 'Input elements should not switch from controlled to uncontrolled (or vice versa). ' + 'Decide between using a controlled or uncontrolled input ' + - 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components', + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' + + ' in input (at **)', ); }); @@ -933,11 +957,12 @@ describe('ReactDOMInput', () => { ReactDOM.render(stub, container); ReactDOM.render(, container); expectDev(console.error.calls.count()).toBe(1); - expectDev(console.error.calls.argsFor(0)[0]).toContain( - 'A component is changing an uncontrolled input of type radio to be controlled. ' + + expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( + 'Warning: A component is changing an uncontrolled input of type radio to be controlled. ' + 'Input elements should not switch from uncontrolled to controlled (or vice versa). ' + 'Decide between using a controlled or uncontrolled input ' + - 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components', + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' + + ' in input (at **)', ); }); @@ -947,11 +972,12 @@ describe('ReactDOMInput', () => { ReactDOM.render(stub, container); ReactDOM.render(, container); expectDev(console.error.calls.count()).toBe(1); - expectDev(console.error.calls.argsFor(0)[0]).toContain( - 'A component is changing an uncontrolled input of type radio to be controlled. ' + + expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( + 'Warning: A component is changing an uncontrolled input of type radio to be controlled. ' + 'Input elements should not switch from uncontrolled to controlled (or vice versa). ' + 'Decide between using a controlled or uncontrolled input ' + - 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components', + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' + + ' in input (at **)', ); }); @@ -1002,11 +1028,12 @@ describe('ReactDOMInput', () => { container, ); ReactDOM.render(, container); - expectDev(console.error.calls.argsFor(0)[0]).toContain( - 'A component is changing a controlled input of type radio to be uncontrolled. ' + + expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( + 'Warning: A component is changing a controlled input of type radio to be uncontrolled. ' + 'Input elements should not switch from controlled to uncontrolled (or vice versa). ' + 'Decide between using a controlled or uncontrolled input ' + - 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components', + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' + + ' in input (at **)', ); }); @@ -1119,22 +1146,20 @@ describe('ReactDOMInput', () => { describe('assigning the value attribute on controlled inputs', function() { function getTestInput() { - return React.createClass({ - getInitialState: function() { - return { - value: this.props.value == null ? '' : this.props.value, - }; - }, - onChange: function(event) { + return class extends React.Component { + state = { + value: this.props.value == null ? '' : this.props.value, + }; + onChange = event => { this.setState({value: event.target.value}); - }, - render: function() { + }; + render() { var type = this.props.type; var value = this.state.value; return ; - }, - }); + } + }; } it('always sets the attribute when values change on text inputs', function() { diff --git a/src/renderers/dom/stack/client/DOMLazyTree.js b/src/renderers/dom/stack/client/DOMLazyTree.js index 7d5b6bb1b649e..a6ef556d5e836 100644 --- a/src/renderers/dom/stack/client/DOMLazyTree.js +++ b/src/renderers/dom/stack/client/DOMLazyTree.js @@ -13,13 +13,10 @@ var DOMNamespaces = require('DOMNamespaces'); var setInnerHTML = require('setInnerHTML'); - +var {DOCUMENT_FRAGMENT_NODE, ELEMENT_NODE} = require('HTMLNodeType'); var createMicrosoftUnsafeLocalFunction = require('createMicrosoftUnsafeLocalFunction'); var setTextContent = require('setTextContent'); -var ELEMENT_NODE_TYPE = 1; -var DOCUMENT_FRAGMENT_NODE_TYPE = 11; - /** * In IE (8-11) and Edge, appending nodes with no children is dramatically * faster than appending a full subtree, so we essentially queue up the @@ -63,8 +60,8 @@ var insertTreeBefore = createMicrosoftUnsafeLocalFunction( // nodes immediately upon insertion into the DOM, so // must also be populated prior to insertion into the DOM. if ( - tree.node.nodeType === DOCUMENT_FRAGMENT_NODE_TYPE || - (tree.node.nodeType === ELEMENT_NODE_TYPE && + tree.node.nodeType === DOCUMENT_FRAGMENT_NODE || + (tree.node.nodeType === ELEMENT_NODE && tree.node.nodeName.toLowerCase() === 'object' && (tree.node.namespaceURI == null || tree.node.namespaceURI === DOMNamespaces.html)) diff --git a/src/renderers/dom/stack/client/ReactDOMComponent.js b/src/renderers/dom/stack/client/ReactDOMComponent.js index 741d65f694cb9..eca9b84fcbebf 100644 --- a/src/renderers/dom/stack/client/ReactDOMComponent.js +++ b/src/renderers/dom/stack/client/ReactDOMComponent.js @@ -29,6 +29,7 @@ var ReactDOMSelect = require('ReactDOMSelect'); var ReactDOMTextarea = require('ReactDOMTextarea'); var ReactInstrumentation = require('ReactInstrumentation'); var ReactMultiChild = require('ReactMultiChild'); +var {DOCUMENT_FRAGMENT_NODE} = require('HTMLNodeType'); var emptyFunction = require('fbjs/lib/emptyFunction'); var escapeTextContentForBrowser = require('escapeTextContentForBrowser'); @@ -54,9 +55,6 @@ var RESERVED_PROPS = { suppressContentEditableWarning: null, }; -// Node type for document fragments (Node.DOCUMENT_FRAGMENT_NODE). -var DOC_FRAGMENT_TYPE = 11; - function getDeclarationErrorAddendum(internalInstance) { if (internalInstance) { var owner = internalInstance._currentElement._owner || null; @@ -135,7 +133,7 @@ function assertValidProps(component, props) { function getDocument(inst) { var containerInfo = inst._hostContainerInfo; var isDocumentFragment = containerInfo._node && - containerInfo._node.nodeType === DOC_FRAGMENT_TYPE; + containerInfo._node.nodeType === DOCUMENT_FRAGMENT_NODE; var doc = isDocumentFragment ? containerInfo._node : containerInfo._ownerDocument; @@ -390,6 +388,9 @@ ReactDOMComponent.Mixin = { assertValidProps(this, props); + if (__DEV__) { + var isCustomComponentTag = isCustomComponent(this._tag, props); + } // We create tags in the namespace of their parent container, except HTML // tags get no namespace. var namespaceURI; @@ -410,8 +411,7 @@ ReactDOMComponent.Mixin = { if (namespaceURI === DOMNamespaces.html) { if (__DEV__) { warning( - isCustomComponent(this._tag, props) || - this._tag === this._currentElement.type, + isCustomComponentTag || this._tag === this._currentElement.type, '<%s /> is using uppercase HTML. Always use lowercase HTML tags ' + 'in React.', this._currentElement.type, @@ -467,17 +467,29 @@ ReactDOMComponent.Mixin = { } else { el = ownerDocument.createElementNS(namespaceURI, type); } - var isCustomComponentTag = isCustomComponent(this._tag, props); - if (__DEV__ && isCustomComponentTag && !didWarnShadyDOM && el.shadyRoot) { - var owner = this._currentElement._owner; - var name = (owner && owner.getName()) || 'A component'; - warning( - false, - '%s is using shady DOM. Using shady DOM with React can ' + - 'cause things to break subtly.', - name, - ); - didWarnShadyDOM = true; + if (__DEV__) { + if (isCustomComponentTag && !didWarnShadyDOM && el.shadyRoot) { + var owner = this._currentElement._owner; + var name = (owner && owner.getName()) || 'A component'; + warning( + false, + '%s is using shady DOM. Using shady DOM with React can ' + + 'cause things to break subtly.', + name, + ); + didWarnShadyDOM = true; + } + if (this._namespaceURI === DOMNamespaces.html) { + warning( + isCustomComponentTag || + Object.prototype.toString.call(el) !== + '[object HTMLUnknownElement]', + 'The tag <%s> is unrecognized in this browser. ' + + 'If you meant to render a React component, start its name with ' + + 'an uppercase letter.', + this._tag, + ); + } } ReactDOMComponentTree.precacheNode(this, el); this._flags |= Flags.hasCachedChildNodes; diff --git a/src/renderers/dom/stack/client/ReactDOMContainerInfo.js b/src/renderers/dom/stack/client/ReactDOMContainerInfo.js index 263c42c7645d7..2e525014b46fb 100644 --- a/src/renderers/dom/stack/client/ReactDOMContainerInfo.js +++ b/src/renderers/dom/stack/client/ReactDOMContainerInfo.js @@ -12,15 +12,14 @@ 'use strict'; var validateDOMNesting = require('validateDOMNesting'); - -var DOC_NODE_TYPE = 9; +var {DOCUMENT_NODE} = require('HTMLNodeType'); function ReactDOMContainerInfo(topLevelWrapper, node) { var info = { _topLevelWrapper: topLevelWrapper, _idCounter: 1, _ownerDocument: node - ? node.nodeType === DOC_NODE_TYPE ? node : node.ownerDocument + ? node.nodeType === DOCUMENT_NODE ? node : node.ownerDocument : null, _node: node, _tag: node ? node.nodeName.toLowerCase() : null, diff --git a/src/renderers/dom/stack/client/ReactDOMTextComponent.js b/src/renderers/dom/stack/client/ReactDOMTextComponent.js index 3ff3a47f92786..55a943d1a9886 100644 --- a/src/renderers/dom/stack/client/ReactDOMTextComponent.js +++ b/src/renderers/dom/stack/client/ReactDOMTextComponent.js @@ -14,6 +14,7 @@ var DOMChildrenOperations = require('DOMChildrenOperations'); var DOMLazyTree = require('DOMLazyTree'); var ReactDOMComponentTree = require('ReactDOMComponentTree'); +var {COMMENT_NODE} = require('HTMLNodeType'); var escapeTextContentForBrowser = require('escapeTextContentForBrowser'); var invariant = require('fbjs/lib/invariant'); @@ -159,7 +160,9 @@ Object.assign(ReactDOMTextComponent.prototype, { 'Missing closing comment for text component %s', this._domID, ); - if (node.nodeType === 8 && node.nodeValue === ' /react-text ') { + if ( + node.nodeType === COMMENT_NODE && node.nodeValue === ' /react-text ' + ) { this._closingComment = node; break; } diff --git a/src/renderers/dom/stack/client/ReactMount.js b/src/renderers/dom/stack/client/ReactMount.js index 93c21c84a2d91..2f567b8367daa 100644 --- a/src/renderers/dom/stack/client/ReactMount.js +++ b/src/renderers/dom/stack/client/ReactMount.js @@ -14,7 +14,6 @@ var DOMLazyTree = require('DOMLazyTree'); var DOMProperty = require('DOMProperty'); var React = require('react'); -var ReactCurrentOwner = require('react/lib/ReactCurrentOwner'); var ReactDOMComponentTree = require('ReactDOMComponentTree'); var ReactDOMContainerInfo = require('ReactDOMContainerInfo'); var ReactDOMFeatureFlags = require('ReactDOMFeatureFlags'); @@ -25,6 +24,7 @@ var ReactMarkupChecksum = require('ReactMarkupChecksum'); var ReactReconciler = require('ReactReconciler'); var ReactUpdateQueue = require('ReactUpdateQueue'); var ReactUpdates = require('ReactUpdates'); +var {ReactCurrentOwner} = require('ReactGlobalSharedState'); var getContextForSubtree = require('getContextForSubtree'); var instantiateReactComponent = require('instantiateReactComponent'); @@ -33,14 +33,15 @@ var setInnerHTML = require('setInnerHTML'); var shouldUpdateReactComponent = require('shouldUpdateReactComponent'); var warning = require('fbjs/lib/warning'); var validateCallback = require('validateCallback'); +var { + DOCUMENT_NODE, + ELEMENT_NODE, + DOCUMENT_FRAGMENT_NODE, +} = require('HTMLNodeType'); var ATTR_NAME = DOMProperty.ID_ATTRIBUTE_NAME; var ROOT_ATTR_NAME = DOMProperty.ROOT_ATTRIBUTE_NAME; -var ELEMENT_NODE_TYPE = 1; -var DOC_NODE_TYPE = 9; -var DOCUMENT_FRAGMENT_NODE_TYPE = 11; - var instancesByReactRootID = {}; /** @@ -69,7 +70,7 @@ function getReactRootElementInContainer(container) { return null; } - if (container.nodeType === DOC_NODE_TYPE) { + if (container.nodeType === DOCUMENT_NODE) { return container.documentElement; } else { return container.firstChild; @@ -181,7 +182,7 @@ function unmountComponentFromNode(instance, container) { ReactInstrumentation.debugTool.onEndFlush(); } - if (container.nodeType === DOC_NODE_TYPE) { + if (container.nodeType === DOCUMENT_NODE) { container = container.documentElement; } @@ -233,9 +234,9 @@ function nodeIsRenderedByOtherInstance(container) { */ function isValidContainer(node) { return !!(node && - (node.nodeType === ELEMENT_NODE_TYPE || - node.nodeType === DOC_NODE_TYPE || - node.nodeType === DOCUMENT_FRAGMENT_NODE_TYPE)); + (node.nodeType === ELEMENT_NODE || + node.nodeType === DOCUMENT_NODE || + node.nodeType === DOCUMENT_FRAGMENT_NODE)); } /** @@ -638,7 +639,7 @@ var ReactMount = { var containerHasNonRootReactChild = hasNonRootReactChild(container); // Check if the container itself is a React root node. - var isContainerReactRoot = container.nodeType === 1 && + var isContainerReactRoot = container.nodeType === ELEMENT_NODE && container.hasAttribute(ROOT_ATTR_NAME); if (__DEV__) { @@ -701,7 +702,7 @@ var ReactMount = { // insert markup into a
    or