diff --git a/lib/backbone-provider.js b/lib/backbone-provider.js
index 9b0236e..303b92f 100644
--- a/lib/backbone-provider.js
+++ b/lib/backbone-provider.js
@@ -1,15 +1,28 @@
-const { Component, Children } = require('react');
+const React = require('react');
+const {Children, Component} = React;
const PropTypes = require('prop-types');
+const ConnectBackboneToReactContext = require('./context.js'); // eslint-disable-line no-unused-vars
class BackboneProvider extends Component {
- getChildContext() {
- return {
- models: this.props.models,
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ models: props.models,
};
}
+ componentDidUpdate(prevProps) {
+ if (this.props.models === prevProps.models) return;
+ this.setState({ models: this.props.models });
+ }
+
render() {
- return Children.only(this.props.children);
+ return (
+
+ {Children.only(this.props.children)}
+
+ );
}
}
@@ -17,9 +30,6 @@ BackboneProvider.propTypes = {
models: PropTypes.object,
children: PropTypes.element.isRequired,
};
-BackboneProvider.childContextTypes = {
- models: PropTypes.object,
-};
BackboneProvider.displayName = 'BackboneProvider';
module.exports = BackboneProvider;
diff --git a/lib/connect-backbone-to-react.js b/lib/connect-backbone-to-react.js
index 7f524fe..bdda2d4 100644
--- a/lib/connect-backbone-to-react.js
+++ b/lib/connect-backbone-to-react.js
@@ -1,7 +1,9 @@
const hoistStatics = require('hoist-non-react-statics');
-const { Component, createElement } = require('react');
+const React = require('react');
+const {Component} = React;
const PropTypes = require('prop-types');
const debounceFn = require('lodash.debounce');
+const ConnectBackboneToReactContext = require('./context.js'); // eslint-disable-line no-unused-vars
function getDisplayName(name) {
return `connectBackboneToReact(${name})`;
@@ -66,39 +68,26 @@ module.exports = function connectBackboneToReact(
const displayName = getDisplayName(wrappedComponentName);
class ConnectBackboneToReact extends Component {
- constructor(props, context) {
- super(props, context);
+ constructor(props) {
+ super(props);
- this.setModels(props, context);
-
- this.state = mapModelsToProps(this.models, this.props);
+ this.models = {};
+ this.state = {};
this.createNewProps = this.createNewProps.bind(this);
+ this.renderChild = this.renderChild.bind(this);
if (debounce) {
const debounceWait = typeof debounce === 'number' ? debounce : 0;
this.createNewProps = debounceFn(this.createNewProps, debounceWait);
}
-
- this.createEventListeners();
}
- setModels(props, context) {
- const models = Object.assign({}, context.models, props.models);
+ setModels(models = {}) {
validateModelTypes(models);
this.models = models;
}
- createEventListeners() {
- Object.keys(this.models).forEach(mapKey => {
- const model = this.models[mapKey];
- // Do not attempt to create event listeners on an undefined model.
- if (!model) return;
-
- this.createEventListener(mapKey, model);
- });
- }
-
createEventListener(modelName, model) {
getEventNames(modelName).forEach(name => {
model.on(name, this.createNewProps, this);
@@ -110,27 +99,34 @@ module.exports = function connectBackboneToReact(
// The only case where this flag is encountered is when this component
// is unmounted within an event handler but the 'all' event is still triggered.
// It is covered in a test case.
- if (this.hasBeenUnmounted) {
- return;
- }
-
+ if (this.hasBeenUnmounted) return;
this.setState(mapModelsToProps(this.models, this.props));
}
- componentWillReceiveProps(nextProps, nextContext) {
- this.setModels(nextProps, nextContext);
- this.createNewProps();
+ renderChild({ models } = {}) {
+ const newModels = Object.assign({}, this.models, models, this.props.models);
// Bind event listeners for each model that changed.
- Object.keys(this.models).forEach(mapKey => {
- const model = this.models[mapKey];
- if ((this.props.models && this.props.models[mapKey] === this.models[mapKey]) ||
- (this.context.models && this.context.models[mapKey] === this.models[mapKey])) {
+ Object.keys(newModels).forEach(mapKey => {
+ const model = newModels[mapKey];
+ if (this.models && this.models[mapKey] === newModels[mapKey]) {
return; // Did not change.
}
- this.createEventListener(mapKey, model);
+ if (model) this.createEventListener(mapKey, model);
+ this.models[mapKey] = model;
});
+ validateModelTypes(this.models);
+
+ const wrappedProps = Object.assign(
+ {},
+ mapModelsToProps(this.models, this.props),
+ this.props
+ );
+
+ // Don't pass through models prop.
+ delete wrappedProps.models;
+ return React.createElement(WrappedComponent, wrappedProps);
}
componentWillUnmount() {
@@ -152,16 +148,11 @@ module.exports = function connectBackboneToReact(
}
render() {
- const wrappedProps = Object.assign(
- {},
- this.state,
- this.props
+ return (
+
+ {this.renderChild}
+
);
-
- // Don't pass through models prop.
- wrappedProps.models = undefined;
-
- return createElement(WrappedComponent, wrappedProps);
}
}
diff --git a/lib/context.js b/lib/context.js
new file mode 100644
index 0000000..2a6f24f
--- /dev/null
+++ b/lib/context.js
@@ -0,0 +1,2 @@
+const React = require('react');
+module.exports = React.createContext('connectBackboneToReact');
diff --git a/package.json b/package.json
index c91ed0a..df11787 100644
--- a/package.json
+++ b/package.json
@@ -27,10 +27,10 @@
"dependencies": {
"hoist-non-react-statics": "^1.2.0",
"lodash.debounce": "^4.0.8",
- "prop-types": "^15.5.8"
+ "prop-types": "^15.7.2"
},
"peerDependencies": {
- "react": "^0.14.0 || ^15.0.0-0 || ^16.0.0-0"
+ "react": "^16.3.0-0"
},
"devDependencies": {
"babel-cli": "^6.22.2",
@@ -38,16 +38,17 @@
"babel-preset-react": "^6.22.0",
"babel-register": "^6.22.0",
"backbone": "^1.3.3",
- "enzyme": "^2.7.1",
- "eslint-config-mongodb-js": "^2.2.0",
+ "enzyme": "^3.10.0",
+ "enzyme-adapter-react-16": "^1.15.1",
+ "eslint-config-mongodb-js": "^2.3.0",
"jsdom": "^9.11.0",
"mocha": "^3.2.0",
"mongodb-js-fmt": "^0.0.3",
"mongodb-js-precommit": "^0.2.8",
"pre-commit": "^1.1.2",
- "react": "^15.4.2",
- "react-addons-test-utils": "^15.4.2",
- "react-dom": "^15.4.2",
+ "react": "^16.12.0",
+ "react-dom": "^16.12.0",
+ "react-test-renderer": "^16.3.2",
"sinon": "^1.17.7",
"standard-version": "^4.0.0"
},
diff --git a/test/backbone-provider.test.js b/test/backbone-provider.test.js
index 6ab5381..17cb173 100644
--- a/test/backbone-provider.test.js
+++ b/test/backbone-provider.test.js
@@ -81,7 +81,7 @@ describe('BackboneProvider', function() {
});
afterEach(function() {
- wrapper.unmount();
+ if (wrapper.exists()) wrapper.unmount();
});
it('passes mapped models and collections as properties to wrapped component', function() {
@@ -139,11 +139,11 @@ describe('BackboneProvider', function() {
const modelsFromContext = wrapper
.find('.name')
- .findWhere((n) => n.text() === userModel.get('name'))
+ .findWhere((n) => !n.type() && n.text() === userModel.get('name'))
.length;
const modelsFromParent = wrapper
.find('.color')
- .findWhere((n) => n.text() === settingsModel.get('color'))
+ .findWhere((n) => !n.type() && n.text() === settingsModel.get('color'))
.length;
// Check that we've rendered data from models passed by both context and the parent component.
@@ -186,12 +186,12 @@ describe('BackboneProvider', function() {
const modelsFromContext = wrapper
.find('.name')
- .findWhere((n) => n.text() === userModel.get('name'))
+ .findWhere((n) => !n.type() && n.text() === userModel.get('name'))
.length;
const modelsFromParent = wrapper
.find('.child-wrapper')
.find('.name')
- .findWhere((n) => n.text() === otherUserModel.get('name'))
+ .findWhere((n) => !n.type() && n.text() === otherUserModel.get('name'))
.length;
// Check that we've given priority to models passed from the parent component.
diff --git a/test/connect-backbone-to-react.test.js b/test/connect-backbone-to-react.test.js
index f2b829a..b4eeab3 100644
--- a/test/connect-backbone-to-react.test.js
+++ b/test/connect-backbone-to-react.test.js
@@ -96,7 +96,7 @@ describe('connectBackboneToReact', function() {
});
afterEach(function() {
- wrapper.unmount();
+ if (wrapper.exists()) wrapper.unmount();
});
it('passes mapped models and collections as properties to wrapped component', function() {
@@ -115,9 +115,10 @@ describe('connectBackboneToReact', function() {
it('updates properties when props function changes models and collections ', function() {
const newName = 'The Loud One';
- stub.props().changeName(newName);
+ stub.prop('changeName')(newName);
+ wrapper.update();
assert.equal(userModel.get('name'), newName);
- assert.equal(stub.props().name, newName);
+ assert.equal(wrapper.find(TestComponent).prop('name'), newName);
assert.equal(setStateSpy.callCount, 4);
});
@@ -125,9 +126,10 @@ describe('connectBackboneToReact', function() {
it('updates properties when model and collections change', function() {
const newName = 'Banana';
userModel.set('name', newName);
+ wrapper.update();
assert.equal(wrapper.find('.name').text(), 'Banana');
assert.equal(userModel.get('name'), newName);
- assert.equal(stub.props().name, newName);
+ assert.equal(wrapper.find(TestComponent).prop('name'), newName);
assert.equal(setStateSpy.callCount, 4);
});
@@ -169,7 +171,7 @@ describe('connectBackboneToReact', function() {
});
afterEach(function() {
- wrapper.unmount();
+ if (wrapper.exists()) wrapper.unmount();
});
it('updates properties when model and collections change', function(done) {
@@ -177,9 +179,10 @@ describe('connectBackboneToReact', function() {
userModel.set('name', newName);
setTimeout(() => {
+ wrapper.update();
assert.equal(wrapper.find('.name').text(), 'Banana');
assert.equal(userModel.get('name'), newName);
- assert.equal(stub.props().name, newName);
+ assert.equal(wrapper.find(TestComponent).prop('name'), newName);
assert.equal(setStateSpy.callCount, 1);
@@ -202,7 +205,7 @@ describe('connectBackboneToReact', function() {
describe('when mounted with an undefined model', function() {
afterEach(function() {
- wrapper.unmount();
+ if (wrapper.exists()) wrapper.unmount();
});
it('the default should mount and unmount the component successfully', function() {
@@ -232,7 +235,7 @@ describe('connectBackboneToReact', function() {
});
afterEach(function() {
- wrapper.unmount();
+ if (wrapper.exists()) wrapper.unmount();
});
it('sets one event handler on the userModel', function() {
@@ -247,9 +250,10 @@ describe('connectBackboneToReact', function() {
it('updates properties when model\'s name changes', function() {
const newName = 'Banana';
userModel.set('name', newName);
+ wrapper.update();
assert.equal(userModel.get('name'), newName);
- assert.equal(stub.props().name, newName);
+ assert.equal(wrapper.find(TestComponent).prop('name'), newName);
});
it('rerenders when tracked property changes', function() {
@@ -291,7 +295,7 @@ describe('connectBackboneToReact', function() {
});
afterEach(function() {
- wrapper.unmount();
+ if (wrapper.exists()) wrapper.unmount();
});
it('sets 0 event handlers on the userModel', function() {
@@ -318,7 +322,7 @@ describe('connectBackboneToReact', function() {
});
afterEach(function() {
- wrapper.unmount();
+ if (wrapper.exists()) wrapper.unmount();
});
it('passes connectedProps through', function() {
@@ -342,7 +346,7 @@ describe('connectBackboneToReact', function() {
});
afterEach(function() {
- wrapper.unmount();
+ if (wrapper.exists()) wrapper.unmount();
});
it('uses default mapModelsToProps function', function() {
@@ -362,8 +366,9 @@ describe('connectBackboneToReact', function() {
it('re-renders props when model changes', function() {
const newName = 'Banana';
userModel.set('name', newName);
+ wrapper.update();
- assert.equal(stub.props().user.name, 'Banana');
+ assert.equal(wrapper.find(TestComponent).prop('user').name, 'Banana');
assert.equal(setStateSpy.callCount, 4);
});
@@ -388,7 +393,7 @@ describe('connectBackboneToReact', function() {
});
afterEach(function() {
- wrapper.unmount();
+ if (wrapper.exists()) wrapper.unmount();
});
it('uses default mapModelsToProps function', function() {
@@ -409,8 +414,9 @@ describe('connectBackboneToReact', function() {
it('re-renders props when model changes', function() {
const newName = 'Banana';
userModel.set('name', newName);
+ wrapper.update();
- assert.equal(stub.props().user.name, 'Banana');
+ assert.equal(wrapper.find(TestComponent).prop('user').name, 'Banana');
assert.equal(setStateSpy.callCount, 1);
});
@@ -523,7 +529,7 @@ describe('connectBackboneToReact', function() {
});
afterEach(function() {
- wrapper.unmount();
+ if (wrapper.exists()) wrapper.unmount();
});
it('retrieves the correct model based on props', function() {
@@ -544,15 +550,15 @@ describe('connectBackboneToReact', function() {
});
describe('when passed props change', function() {
- let setStateSpy;
let newName;
let newAge;
let newUserModel;
beforeEach(function() {
+ // Disable no-unused-vars on the next line because the current version doesn't
+ // detect that is a usage.
+ // eslint-disable-next-line
const ConnectedTest = connectBackboneToReact(mapModelsToProps)(TestComponent);
- setStateSpy = sandbox.spy(ConnectedTest.prototype, 'setState');
-
wrapper = mount();
stub = wrapper.find(TestComponent);
@@ -573,11 +579,7 @@ describe('connectBackboneToReact', function() {
});
afterEach(function() {
- wrapper.unmount();
- });
-
- it('calls setState once', function() {
- assert.equal(setStateSpy.calledOnce, true);
+ if (wrapper.exists()) wrapper.unmount();
});
it('renders the new props', function() {
diff --git a/test/test-setup.js b/test/test-setup.js
index d63b469..1f9180b 100644
--- a/test/test-setup.js
+++ b/test/test-setup.js
@@ -1,4 +1,7 @@
require('babel-register');
+const Enzyme = require('enzyme');
+const Adapter = require('enzyme-adapter-react-16');
+Enzyme.configure({ adapter: new Adapter() });
const jsdom = require('jsdom').jsdom;
global.document = jsdom('');