diff --git a/lib/__tests__/fixtures/components/User.jsx b/lib/__tests__/fixtures/components/User.jsx index beea08f..5dabffc 100755 --- a/lib/__tests__/fixtures/components/User.jsx +++ b/lib/__tests__/fixtures/components/User.jsx @@ -1,3 +1,4 @@ +import PropTypes from 'prop-types'; import React from 'react'; import { deps } from '../../../'; @@ -11,11 +12,11 @@ export default deps(({ userId }) => ({ static displayName = 'User'; static propTypes = { - deleteUser: React.PropTypes.func, - rank: React.PropTypes.string, - updateUser: React.PropTypes.func, - userId: React.PropTypes.number, - userName: React.PropTypes.string, + deleteUser: PropTypes.func, + rank: PropTypes.string, + updateUser: PropTypes.func, + userId: PropTypes.number, + userName: PropTypes.string, }; constructor(props) { diff --git a/lib/__tests__/fixtures/components/Users.jsx b/lib/__tests__/fixtures/components/Users.jsx index acf8a66..baceec0 100755 --- a/lib/__tests__/fixtures/components/Users.jsx +++ b/lib/__tests__/fixtures/components/Users.jsx @@ -1,3 +1,4 @@ +import PropTypes from 'prop-types'; import React from 'react'; import { deps, Store } from '../../../'; @@ -17,14 +18,14 @@ export default deps(() => ({ static displayName = 'Users'; static propTypes = { - createUser: React.PropTypes.func, + createUser: PropTypes.func, optionalStore: Store.State.propType(React.any), - toggleUsersVisibility: React.PropTypes.func, - uiUsersVisibility: Store.State.propType(React.PropTypes.bool.isRequired).isRequired, - users: Store.State.propType(React.PropTypes.arrayOf(React.PropTypes.shape({ - userId: React.PropTypes.number.isRequired, - userName: React.PropTypes.string.isRequired, - rank: React.PropTypes.string.isRequired, + toggleUsersVisibility: PropTypes.func, + uiUsersVisibility: Store.State.propType(PropTypes.bool.isRequired).isRequired, + users: Store.State.propType(PropTypes.arrayOf(PropTypes.shape({ + userId: PropTypes.number.isRequired, + userName: PropTypes.string.isRequired, + rank: PropTypes.string.isRequired, }))).isRequired, }; diff --git a/lib/__tests__/node/components.js b/lib/__tests__/node/components.js index 681565d..30699f0 100644 --- a/lib/__tests__/node/components.js +++ b/lib/__tests__/node/components.js @@ -1,4 +1,5 @@ -import React, { Component, PropTypes as T } from 'react'; +import PropTypes from 'prop-types'; +import React, { Component } from 'react'; import stores from '../../stores'; import Store from '../../Store'; import MemoryStore from '../../MemoryStore'; @@ -18,7 +19,7 @@ describe('stores', () => { class Bar extends Component { static displayName = 'Bar'; static propTypes = { - foo: Store.State.propType(T.string), + foo: Store.State.propType(PropTypes.string), }; render() { const { foo } = this.props; diff --git a/lib/__tests__/node/prepare.js b/lib/__tests__/node/prepare.js index bdc072f..5968acc 100644 --- a/lib/__tests__/node/prepare.js +++ b/lib/__tests__/node/prepare.js @@ -3,6 +3,7 @@ import Promise from 'bluebird'; import HTTPStatus from 'http-status-codes'; import _ from 'lodash'; import nock from 'nock'; +import PropTypes from 'prop-types'; import React from 'react'; import ReactDOMServer from 'react-dom/server'; import should from 'should/as-function'; @@ -91,7 +92,15 @@ describe('prepare', () => { should(prepareInner).have.property('calledOnce').which.is.exactly(true); }); }); - + it('prepares stateless components correctly', async function test() { + const StatelessComponent = () => null; + StatelessComponent[preparable.$prepare] = async function preps(props, context) { + context.bar = await Promise.resolve('foo'); + }; + const context = {}; + return await prepare(, context) + .then(() => should(context).have.property('bar').which.is.exactly('foo')); + }); it('doesn’t fetch the same HTTPStore more than once', async function test() { const testHttpConf = { protocol: 'http', @@ -112,7 +121,7 @@ describe('prepare', () => { @stores(getStoreDeps) class Child extends React.Component { static propTypes = { - testStore: React.PropTypes.object, + testStore: PropTypes.object, }; render() { return {this.props.testStore.value.message}; diff --git a/lib/actions.js b/lib/actions.js index 71dbcb4..a7a9e83 100755 --- a/lib/actions.js +++ b/lib/actions.js @@ -1,10 +1,11 @@ +import PropTypes from 'prop-types'; import React from 'react'; -import PureRenderMixin from 'react-addons-pure-render-mixin'; import _ from 'lodash'; const __DEV__ = process && process.env && process.env.NODE_ENV === 'development'; import Flux from './Flux'; import defaultFluxKey from './defaultFluxKey'; +import shouldPureComponentUpdate from './util/shouldPureComponentUpdate'; /** * Enhance a React Component and make context's {@link Flux}'s {@link Actions}s requested by bindings avaliable as props. @@ -18,7 +19,7 @@ import defaultFluxKey from './defaultFluxKey'; function actions(getBindings, { displayName = void 0, fluxKey = defaultFluxKey, - shouldNexusComponentUpdate = PureRenderMixin.shouldComponentUpdate, + shouldNexusComponentUpdate = shouldPureComponentUpdate, } = {}) { return (Component) => { /** @@ -28,7 +29,7 @@ function actions(getBindings, { static displayName = displayName || `@actions(${Component.displayName})`; static contextTypes = { - [fluxKey]: React.PropTypes.instanceOf(Flux), + [fluxKey]: PropTypes.instanceOf(Flux), }; /** diff --git a/lib/preparable.js b/lib/preparable.js index 71c95a7..b3611f5 100755 --- a/lib/preparable.js +++ b/lib/preparable.js @@ -1,6 +1,4 @@ -import React from 'react'; - -import isExtensionOf from './util/isExtensionOf'; +import isReactComponent from './util/isReactComponent'; const $prepare = Symbol('preparable'); /** @@ -14,7 +12,7 @@ function preparable(prepare) { throw new TypeError('@preparable() should be passed an async function'); } return function extendComponent(Component) { - if(!isExtensionOf(Component, React.Component)) { + if(!isReactComponent(Component)) { throw new TypeError('@preparable should only be applied to React Components'); } return class extends Component { diff --git a/lib/prepare.js b/lib/prepare.js index c6d26af..2d0b729 100755 --- a/lib/prepare.js +++ b/lib/prepare.js @@ -2,7 +2,7 @@ import Promise from 'bluebird'; import React from 'react'; import _ from 'lodash'; -import isExtensionOf from './util/isExtensionOf'; +import isReactComponent from './util/isReactComponent'; import preparable from './preparable'; const { $prepare } = preparable; @@ -81,12 +81,12 @@ async function prepareElement(element, context) { if(typeof type === 'string') { return [props.children, context]; } + await satisfy(element, context); // Function component (new in react 0.14.x) - if(!isExtensionOf(type, React.Component)) { + if(!isReactComponent(type)) { return [type(props), context]; } // Composite element - await satisfy(element, context); let inst = null; try { inst = create(type, props, context); diff --git a/lib/root.js b/lib/root.js index ff181d9..188fe91 100644 --- a/lib/root.js +++ b/lib/root.js @@ -1,3 +1,4 @@ +import PropTypes from 'prop-types'; import React from 'react'; import Flux from './Flux'; @@ -20,11 +21,11 @@ function root({ fluxKey = defaultFluxKey, displayName = void 0 } = {}) { static displayName = displayName || `@root(${Component.displayName})`; static propTypes = { - flux: React.PropTypes.instanceOf(Flux), + flux: PropTypes.instanceOf(Flux), }; static childContextTypes = { - [fluxKey]: React.PropTypes.instanceOf(Flux), + [fluxKey]: PropTypes.instanceOf(Flux), }; getChildContext() { diff --git a/lib/stores.js b/lib/stores.js index c916d4a..82ba796 100755 --- a/lib/stores.js +++ b/lib/stores.js @@ -1,6 +1,6 @@ import Promise from 'bluebird'; +import PropTypes from 'prop-types'; import React from 'react'; -import PureRenderMixin from 'react-addons-pure-render-mixin'; import _ from 'lodash'; import deepEqual from 'deep-equal'; const __DEV__ = process && process.env && process.NODE_ENV === 'development'; @@ -9,6 +9,7 @@ import defaultFluxKey from './defaultFluxKey'; import diff from './util/diff'; import Flux from './Flux'; import preparable from './preparable'; +import shouldPureComponentUpdate from './util/shouldPureComponentUpdate'; /** * Enhance a React Component and make context's {@link Flux}'s {@link Store}'s {@link State}s requested by bindings @@ -24,7 +25,7 @@ function stores( getBindings, { displayName = void 0, fluxKey = defaultFluxKey, - shouldNexusComponentUpdate = PureRenderMixin.shouldComponentUpdate, + shouldNexusComponentUpdate = shouldPureComponentUpdate, } = {}) { return (Component) => preparable(async function prepare(props, context) { const flux = context[fluxKey]; @@ -46,7 +47,7 @@ function stores( static displayName = displayName || `@stores(${Component.displayName})`; static contextTypes = { - [fluxKey]: React.PropTypes.instanceOf(Flux), + [fluxKey]: PropTypes.instanceOf(Flux), }; /** diff --git a/lib/util/__tests__/isExtensionOf.js b/lib/util/__tests__/isExtensionOf.js deleted file mode 100644 index 632dc81..0000000 --- a/lib/util/__tests__/isExtensionOf.js +++ /dev/null @@ -1,22 +0,0 @@ -const { describe, it } = global; -import isExtensionOf from '../isExtensionOf'; -import should from 'should/as-function'; - -describe('isExtensionOf(B, A)', () => { - it('returns true when B extends A', () => { - class A {} - class B extends A {} - should(isExtensionOf(B, A)).be.true(); - }); - it('returns false when B doesn\'t extend A', () => { - class A {} - class B {} - should(isExtensionOf(B, A)).not.be.true(); - }); - it('returns true when B extends C which extends A', () => { - class A {} - class C extends A {} - class B extends C {} - should(isExtensionOf(B, A)).be.true(); - }); -}); diff --git a/lib/util/__tests__/isReactComponent.js b/lib/util/__tests__/isReactComponent.js new file mode 100644 index 0000000..35e65c4 --- /dev/null +++ b/lib/util/__tests__/isReactComponent.js @@ -0,0 +1,27 @@ +import React from 'react'; +import isReactComponent from '../isReactComponent'; +import should from 'should/as-function'; +const { describe, it } = global; + +describe('isReactComponent(type)', () => { + it('returns true on React.Component', () => { + class C extends React.Component { + render() { + return null; + } + } + should(isReactComponent(C)).be.true(); + }); + it('returns true on React.PureComponent', () => { + class C extends React.PureComponent { + render() { + return null; + } + } + should(isReactComponent(C)).be.true(); + }); + it('returns false on a functional component', () => { + const C = () => (
{'foo'}
); + should(isReactComponent(C)).be.false(); + }); +}); diff --git a/lib/util/isExtensionOf.js b/lib/util/isExtensionOf.js deleted file mode 100755 index aa79732..0000000 --- a/lib/util/isExtensionOf.js +++ /dev/null @@ -1,22 +0,0 @@ -/** - * Determines whether a class A is an extension (direct or indirect) of another ancestor class B, by recursively - * walking up the proto chain. - * @param {Function} A Class that could be an extension - * @param {Function} B Class that could be an ancestor - * @return {Boolean} True if A is an extension of B, false otherwise. - */ -function isExtensionOf(A, B) { - if(A === B) { - return true; - } - if(typeof A !== 'function') { - return false; - } - const protoOfA = Reflect.getPrototypeOf(A); - if(typeof B !== 'function') { - return protoOfA === B; - } - return isExtensionOf(protoOfA, B); -} - -export default isExtensionOf; diff --git a/lib/util/isReactComponent.js b/lib/util/isReactComponent.js new file mode 100755 index 0000000..996364f --- /dev/null +++ b/lib/util/isReactComponent.js @@ -0,0 +1,6 @@ +export default function isReactCompositeComponent(component) { + if(typeof component.prototype === 'object' && component.prototype.isReactComponent) { + return true; + } + return false; +} diff --git a/lib/util/shouldPureComponentUpdate.js b/lib/util/shouldPureComponentUpdate.js new file mode 100644 index 0000000..7fa48f9 --- /dev/null +++ b/lib/util/shouldPureComponentUpdate.js @@ -0,0 +1,7 @@ +import shallowEqual from 'shallowequal'; + +function shouldPureComponentUpdate(nextProps, nextState) { + return (!shallowEqual(this.props, nextProps) || !shallowEqual(this.state, nextState)); +} + +export default shouldPureComponentUpdate; diff --git a/package.json b/package.json index 00df83c..e090b5d 100644 --- a/package.json +++ b/package.json @@ -100,10 +100,10 @@ "koa-router": "^5.3.0", "mocha": "^2.3.4", "nock": "^8.0.0", - "react": "^15.3.0", - "react-addons-pure-render-mixin": "^15.3.0", - "react-addons-test-utils": "^15.3.0", - "react-dom": "^15.3.0", + "prop-types": "^15.6.0", + "react": "^15.6.2", + "react-dom": "^15.6.2", + "react-test-renderer": "^16.0.0", "rimraf": "^2.5.0", "run-sequence": "^1.1.5", "selenium-standalone": "^4.9.0", @@ -125,10 +125,10 @@ "lodash": "^4.0.0", "path-to-regexp": "^1.2.1", "setimmediate": "^1.0.4", - "should": "^8.1.1" + "shallowequal": "^1.0.2" }, "peerDependencies": { - "react": "^15.3.0", - "react-dom": "^15.3.0" + "react": "^15.6.2", + "react-dom": "^15.6.2" } }