Skip to content

Commit e66c1fb

Browse files
committed
1 parent 747c263 commit e66c1fb

File tree

1 file changed

+164
-26
lines changed

1 file changed

+164
-26
lines changed

src/ReactMeteor.js

Lines changed: 164 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,176 @@
11
var ReactMeteorMixin = {
2-
_handleMeteorChange: function () {
3-
if (this.getMeteorState !== undefined) {
4-
this.setState(this.getMeteorState())
5-
}
6-
},
2+
componentWillMount: function() {
3+
var self = this;
4+
5+
self._meteorStateDep = new Tracker.Dependency();
6+
self._meteorFirstRun = true;
77

8-
_cancelComputation: function () {
9-
if (this._meteorComputation) {
10-
this._meteorComputation.stop()
11-
this._meteorComputation = null
8+
if (Meteor.isClient) {
9+
Tracker.autorun(function(computation) {
10+
self._meteorComputation = computation;
11+
self._meteorStateDep.depend();
12+
13+
if (self.startMeteorSubscriptions) {
14+
// Calling this method in a Tracker.autorun callback will ensure
15+
// that the subscriptions are canceled when the computation stops.
16+
self.startMeteorSubscriptions();
1217
}
13-
},
1418

15-
componentWillMount: function () {
16-
this._meteorComputation = Tracker.autorun(this._handleMeteorChange)
17-
},
19+
enqueueMeteorStateUpdate(self);
20+
});
21+
22+
} else {
23+
enqueueMeteorStateUpdate(self);
24+
}
25+
},
26+
27+
componentWillUpdate: function(nextProps, nextState) {
28+
if (this._meteorCalledSetState) {
29+
// If this component update was triggered by the ReactMeteor.Mixin,
30+
// then we do not want to trigger the change event again, because
31+
// that would lead to an infinite update loop.
32+
this._meteorCalledSetState = false;
33+
return;
34+
}
35+
36+
if (this._meteorStateDep) {
37+
this._meteorStateDep.changed();
38+
}
39+
},
40+
41+
componentWillUnmount: function() {
42+
if (this._meteorComputation) {
43+
this._meteorComputation.stop();
44+
this._meteorComputation = null;
45+
}
46+
}
47+
};
48+
49+
function enqueueMeteorStateUpdate(component) {
50+
var partialState =
51+
component.getMeteorState &&
52+
component.getMeteorState();
53+
54+
if (! partialState) {
55+
// The getMeteorState method can return a falsy value to avoid
56+
// triggering a state update.
57+
return;
58+
}
59+
60+
if (component._meteorFirstRun) {
61+
// If it's the first time we've called enqueueMeteorStateUpdate since
62+
// the component was mounted, set the state synchronously.
63+
component._meteorFirstRun = false;
64+
component._meteorCalledSetState = true;
65+
component.setState(partialState);
66+
return;
67+
}
68+
69+
Tracker.afterFlush(function() {
70+
component._meteorCalledSetState = true;
71+
component.setState(partialState);
72+
});
73+
}
1874

19-
componentWillReceiveProps: function (nextProps) {
20-
var oldProps = this.props
21-
this.props = nextProps
22-
this._handleMeteorChange()
23-
this.props = oldProps
24-
},
75+
// Like React.render, but it replaces targetNode, and works even if
76+
// targetNode.parentNode has children other than targetNode.
77+
function renderInPlaceOfNode(reactElement, targetNode) {
78+
var container = targetNode.parentNode;
79+
var prevSibs = [];
80+
var nextSibs = [];
81+
var sibs = prevSibs;
82+
var child = container.firstChild;
2583

26-
componentWillUnmount: function () {
27-
this._cancelComputation()
84+
while (child) {
85+
if (child === targetNode) {
86+
sibs = nextSibs;
87+
} else {
88+
sibs.push(child);
2889
}
90+
var next = child.nextSibling;
91+
container.removeChild(child);
92+
child = next;
93+
}
94+
95+
var result = React.render(reactElement, container);
96+
var rendered = container.firstChild;
97+
98+
if (prevSibs.length > 0) {
99+
prevSibs.forEach(function(sib) {
100+
container.insertBefore(sib, rendered);
101+
});
102+
}
103+
104+
if (nextSibs.length > 0) {
105+
nextSibs.forEach(function(sib) {
106+
container.appendChild(sib);
107+
});
108+
}
109+
110+
return result;
29111
}
30112

113+
function unmountComponent(reactComponent) {
114+
var rootNode = React.findDOMNode(reactComponent);
115+
var container = rootNode && rootNode.parentNode;
116+
117+
if (container) {
118+
var siblings = [];
119+
var sibling = container.firstChild;
120+
121+
while (sibling) {
122+
var next = sibling.nextSibling;
123+
if (sibling !== rootNode) {
124+
siblings.push(sibling);
125+
container.removeChild(sibling);
126+
}
127+
sibling = next;
128+
}
129+
130+
React.unmountComponentAtNode(container);
31131

32-
if (typeof exports === "object") {
33-
ReactMeteor = exports
34-
} else {
35-
ReactMeteor = {}
132+
siblings.forEach(function (sib) {
133+
container.appendChild(sib);
134+
});
135+
}
36136
}
37137

38-
ReactMeteor.Mixin = ReactMeteorMixin
138+
ReactMeteor = {
139+
Mixin: ReactMeteorMixin,
140+
141+
// So you don't have to mix in ReactMeteor.Mixin explicitly.
142+
createClass: function createClass(spec) {
143+
spec.mixins = spec.mixins || [];
144+
spec.mixins.push(ReactMeteorMixin);
145+
var Cls = React.createClass(spec);
146+
147+
if (Meteor.isClient &&
148+
typeof Template === "function" &&
149+
typeof spec.templateName === "string") {
150+
var template = new Template(
151+
spec.templateName,
152+
function() {
153+
// A placeholder HTML element that will serve as the mounting
154+
// point for the React component. May have siblings!
155+
return new HTML.SPAN;
156+
}
157+
);
158+
159+
template.onRendered(function() {
160+
this._reactComponent = renderInPlaceOfNode(
161+
// Equivalent to <Cls {...this.data} />:
162+
React.createElement(Cls, this.data || {}),
163+
this.find("span")
164+
);
165+
});
166+
167+
template.onDestroyed(function() {
168+
unmountComponent(this._reactComponent);
169+
});
170+
171+
Template[spec.templateName] = template;
172+
}
173+
174+
return Cls;
175+
}
176+
};

0 commit comments

Comments
 (0)