diff --git a/lib/mongo-id.js b/lib/mongo-id.js
index 385da09..24c91bd 100644
--- a/lib/mongo-id.js
+++ b/lib/mongo-id.js
@@ -1,7 +1,7 @@
//https://github.com/meteor/meteor/tree/master/packages/mongo-id
import EJSON from "ejson";
-MongoID = {};
+const MongoID = {};
MongoID._looksLikeObjectID = function (str) {
return str.length === 24 && str.match(/^[0-9a-f]*$/);
diff --git a/package-lock.json b/package-lock.json
index 34947e0..c1608d8 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1093,15 +1093,12 @@
"resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz",
"integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY="
},
- "create-react-class": {
- "version": "15.6.2",
- "resolved": "https://registry.npmjs.org/create-react-class/-/create-react-class-15.6.2.tgz",
- "integrity": "sha1-zx7RXxKq1/FO9fLf4F5sQvke8Co=",
- "requires": {
- "fbjs": "0.8.16",
- "loose-envify": "1.3.1",
- "object-assign": "4.1.1"
- }
+ "core-util-is": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
+ "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
+ "dev": true,
+ "optional": true
},
"crypto-js": {
"version": "3.1.8",
diff --git a/package.json b/package.json
index 5f592db..7347b85 100644
--- a/package.json
+++ b/package.json
@@ -27,7 +27,6 @@
"homepage": "https://github.com/inProgress-team/react-native-meteor#readme",
"dependencies": {
"base-64": "^0.1.0",
- "create-react-class": "^15.6.0",
"crypto-js": "^3.1.6",
"ejson": "^2.1.2",
"minimongo-cache": "0.0.48",
diff --git a/src/components/MeteorDataManager.js b/src/components/MeteorDataManager.js
new file mode 100644
index 0000000..9336d8a
--- /dev/null
+++ b/src/components/MeteorDataManager.js
@@ -0,0 +1,116 @@
+import Trackr from 'trackr';
+import Data from '../Data';
+
+// A class to keep the state and utility methods needed to manage
+// the Meteor data for a component.
+class MeteorDataManager {
+ constructor(component) {
+ this.component = component;
+ this.computation = null;
+ this.oldData = null;
+ this._meteorDataDep = new Trackr.Dependency();
+ this._meteorDataChangedCallback = () => { this._meteorDataDep.changed(); };
+
+ Data.onChange(this._meteorDataChangedCallback);
+ }
+
+ dispose() {
+ if (this.computation) {
+ this.computation.stop();
+ this.computation = null;
+ }
+
+ Data.offChange(this._meteorDataChangedCallback);
+ }
+
+ calculateData() {
+ const component = this.component;
+
+ if (!component.getMeteorData) {
+ return null;
+ }
+
+ if (this.computation) {
+ this.computation.stop();
+ this.computation = null;
+ }
+
+ let data;
+ // Use Tracker.nonreactive in case we are inside a Tracker Computation.
+ // This can happen if someone calls `ReactDOM.render` inside a Computation.
+ // In that case, we want to opt out of the normal behavior of nested
+ // Computations, where if the outer one is invalidated or stopped,
+ // it stops the inner one.
+
+ this.computation = Trackr.nonreactive(() => {
+ return Trackr.autorun((c) => {
+ this._meteorDataDep.depend();
+ if (c.firstRun) {
+ const savedSetState = component.setState;
+ try {
+ component.setState = () => {
+ throw new Error(
+"Can't call `setState` inside `getMeteorData` as this could cause an endless" +
+" loop. To respond to Meteor data changing, consider making this component" +
+" a \"wrapper component\" that only fetches data and passes it in as props to" +
+" a child component. Then you can use `componentWillReceiveProps` in that" +
+" child component.");
+ };
+
+ data = component.getMeteorData();
+ } finally {
+ component.setState = savedSetState;
+ }
+
+ } else {
+ // Stop this computation instead of using the re-run.
+ // We use a brand-new autorun for each call to getMeteorData
+ // to capture dependencies on any reactive data sources that
+ // are accessed. The reason we can't use a single autorun
+ // for the lifetime of the component is that Tracker only
+ // re-runs autoruns at flush time, while we need to be able to
+ // re-call getMeteorData synchronously whenever we want, e.g.
+ // from componentWillUpdate.
+ c.stop();
+ // Calling forceUpdate() triggers componentWillUpdate which
+ // recalculates getMeteorData() and re-renders the component.
+ try {
+ component.forceUpdate();
+ } catch(e) {
+ console.error(e);
+ }
+ }
+ });
+ });
+
+ return data;
+ }
+
+ updateData(newData) {
+ const component = this.component;
+ const oldData = this.oldData;
+
+ if (! (newData && (typeof newData) === 'object')) {
+ throw new Error("Expected object returned from getMeteorData");
+ }
+ // update componentData in place based on newData
+ for (let key in newData) {
+ component.data[key] = newData[key];
+ }
+ // if there is oldData (which is every time this method is called
+ // except the first), delete keys in newData that aren't in
+ // oldData. don't interfere with other keys, in case we are
+ // co-existing with something else that writes to a component's
+ // this.data.
+ if (oldData) {
+ for (let key in oldData) {
+ if (!(key in newData)) {
+ delete component.data[key];
+ }
+ }
+ }
+ this.oldData = newData;
+ }
+}
+
+export default MeteorDataManager;
diff --git a/src/components/createContainer.js b/src/components/createContainer.js
index 040df15..c649bb5 100644
--- a/src/components/createContainer.js
+++ b/src/components/createContainer.js
@@ -1,32 +1,80 @@
-/**
- * Container helper using react-meteor-data.
- */
-
import React from 'react';
-import createReactClass from 'create-react-class';
+import EJSON from 'ejson';
-import Mixin from './Mixin';
+import Data from '../Data';
+import MeteorDataManager from './MeteorDataManager';
-export default function createContainer(options = {}, Component) {
- let expandedOptions = options;
- if (typeof options === 'function') {
- expandedOptions = {
- getMeteorData: options,
- };
- }
+export default function createContainer(mapMeteorDataToProps, WrappedComponent) {
+ class componentWithMeteorContainer extends React.Component {
+ constructor(props) {
+ super(props);
- const {
- getMeteorData
- } = expandedOptions;
+ this.getMeteorData = this.getMeteorData.bind(this);
+ }
- return createReactClass({
- displayName: 'MeteorDataContainer',
- mixins: [Mixin],
getMeteorData() {
- return getMeteorData(this.props);
- },
+ return mapMeteorDataToProps(this.props);
+ }
+
+ componentWillMount() {
+ Data.waitDdpReady(() => {
+ if (this.getMeteorData) {
+ this.data = {};
+ this._meteorDataManager = new MeteorDataManager(this);
+ const newData = this._meteorDataManager.calculateData();
+ this._meteorDataManager.updateData(newData);
+ }
+ });
+ }
+
+ componentWillUpdate(nextProps, nextState) {
+ if(this.startMeteorSubscriptions) {
+ if(!EJSON.equals(this.state, nextState) || !EJSON.equals(this.props, nextProps)) {
+ this._meteorSubscriptionsManager._meteorDataChangedCallback()
+ }
+ }
+
+ if (this.getMeteorData) {
+ const saveProps = this.props;
+ const saveState = this.state;
+ let newData;
+ try {
+ // Temporarily assign this.state and this.props,
+ // so that they are seen by getMeteorData!
+ // This is a simulation of how the proposed Observe API
+ // for React will work, which calls observe() after
+ // componentWillUpdate and after props and state are
+ // updated, but before render() is called.
+ // See https://github.com/facebook/react/issues/3398.
+ this.props = nextProps;
+ this.state = nextState;
+ newData = this._meteorDataManager.calculateData();
+ } finally {
+ this.props = saveProps;
+ this.state = saveState;
+ }
+
+ this._meteorDataManager.updateData(newData);
+ }
+ }
+
+ componentWillUnmount() {
+ if (this._meteorDataManager) {
+ this._meteorDataManager.dispose();
+ }
+
+ if (this._meteorSubscriptionsManager) {
+ this._meteorSubscriptionsManager.dispose();
+ }
+ }
+
render() {
- return ;
- },
- });
+ return ;
+ }
+ }
+
+ const newDisplayName = WrappedComponent.displayName || WrappedComponent.name || 'Component';
+ componentWithMeteorContainer.displayName = `WithMeteorContainer(${newDisplayName})`;
+
+ return componentWithMeteorContainer;
}