-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathreact-es6-component.js
184 lines (174 loc) · 5.47 KB
/
react-es6-component.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
React.MeteorComponent = class ReactMeteorComponent extends React.Component
{
// SJS extra stuff
getMeteorData () // dummy to prevent errors if not re-defined in subclass
{
return {};
};
constructor (props)
{
super (props);
SteinitzREA.AutoBind (this);
}
componentWillMount () // some documents say this stuff should go in the constructor for this ES6 technique
{
this.data = {};
this._meteorDataManager = new MeteorDataManager (this);
var newData = this._meteorDataManager.calculateData ();
this._meteorDataManager.updateData (newData);
}
componentWillUpdate (nextProps, nextState)
{
var saveProps = this.props;
var saveState = this.state;
var newData = undefined;
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 ()
{
this._meteorDataManager.dispose ();
}
};
// A class to keep the state and utility methods needed to manage the Meteor data for a component.
var MeteorDataManager =
function (component)
{
babelHelpers.classCallCheck (this, MeteorDataManager); // SJS - what does this do?
this.component = component;
this.computation = null;
this.oldData = null;
this.dispose =
function dispose()
{
if (this.computation)
{
this.computation.stop();
this.computation = null;
}
};
this.calculateData =
function calculateData()
{
var component = this.component;
var props = component.props;
var state = component.state;
if (!component.getMeteorData)
{
return null;
}
// When rendering on the server, we don't want to use the Tracker. We only
// do the first rendering on the server so we can get the data right away
if (Meteor.isServer)
{
return component.getMeteorData ();
}
if (this.computation)
{
this.computation.stop();
this.computation = null;
}
var data = undefined;
// 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 = Tracker.nonreactive (
function ()
{
return Tracker.autorun (
function (computation)
{
if (computation.firstRun)
{
var savedSetState = component.setState;
try
{
component.setState = function ()
{
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.
computation.stop();
// Calling forceUpdate() triggers componentWillUpdate which
// recalculates getMeteorData() and re-renders the component.
// console.log ("MeteorDataManager.calculateData - calling component.forceUpdate");
component.forceUpdate ();
}
}
);
}
);
if (Package.mongo && Package.mongo.Mongo)
{
Object.keys (data).forEach (
function (key)
{
if (data [key] instanceof Package.mongo.Mongo.Cursor)
{
console.warn ("Warning: you are returning a Mongo cursor from getMeteorData. This value " + "will not be reactive. You probably want to call `.fetch()` on the cursor " + "before returning it.");
}
}
);
}
return data;
};
this.updateData =
function updateData (newData)
{
var component = this.component;
var oldData = this.oldData;
if (!(newData && typeof newData === 'object'))
{
throw new Error ("Expected object returned from getMeteorData");
}
// update componentData in place based on newData
for (var key in babelHelpers.sanitizeForInObject (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 (var key in babelHelpers.sanitizeForInObject (oldData))
{
if (!(key in newData))
{
delete component.data [key];
}
}
}
this.oldData = newData;
};
};