77 | )
78 | }
79 | }
80 |
81 | /* React
82 | Render into main.html, target the div with id="root". React can live next to other elements inside the main html
83 | document, i.e. legacy templates/engines.
84 | */
85 | Meteor.startup(function () {
86 | ReactDOM.render(, document.getElementById('root'));
87 | });
88 |
89 |
90 | /***********************************************************************************************************************
91 | *
92 | * [CollectionShowcase]
93 | * This component uses TrackerReact to provide real-time data from the "tasks" Meteor Collection.
94 | * Make sure collections are also instantiated on the server, published and subscribed on the client.
95 | *
96 | **********************************************************************************************************************/
97 |
98 | /* Meteor
99 | Install TrackerReact via "meteor add ultimatejs:tracker-react" and import the default composition helper
100 | "TrackerReact" via Meteors module system (meteor/author:package-name).
101 | */
102 | import TrackerReact from 'meteor/ultimatejs:tracker-react';
103 |
104 | /* Meteor
105 | Initialise the "tasks" collection on the client. The same is done on the server. See
106 | "/server/dataPublications.js".
107 | */
108 | Tasks = new Mongo.Collection("tasks");
109 |
110 | /* Meteor, React -> TrackerReact
111 | In order to have react re-render on data invalidation and update our component, we need to compose it with
112 | TrackerReact. If we would not do so, everything would still work fine but updates are only shown on page/component
113 | re-load. -> "TrackerReact(React.Component)"
114 |
115 | Profiler: Set {profiler: false} to turn profile logs off. If not used, this second argument can be omitted.
116 | */
117 | class CollectionShowcase extends TrackerReact(React.Component) {
118 |
119 | /* Meteor, React
120 | In this example, the Meteor data is only used inside this react component. Therefore, no active data subscription
121 | exists up on rendering this component. To make sure we have all the necessary data up on rendering, we subscribe
122 | to our data when the component is about to render.
123 |
124 | The "constructor()" method is called before rendering and therefore a good place to start our subscription. We
125 | could also subscribe outside of the react life cycle, i.e., simply at the beginning of the page.
126 | */
127 | constructor() {
128 | super();
129 | /* React
130 | Data subscription(s) define our state (availability of data), so it should also be assigned to it (return object).
131 | */
132 | this.state = {
133 | subscription: {
134 | tasks: Meteor.subscribe('tasks')
135 | }
136 | }
137 | }
138 |
139 | /* Meteor, React
140 | Since we are not planning to use this data anywhere else, we will stop the subscription when the component unmounts.
141 | */
142 | //noinspection JSUnusedGlobalSymbols
143 | componentWillUnmount() {
144 | this.state.subscription.tasks.stop();
145 | }
146 |
147 | /* Meteor, React
148 | As mentioned before, data subscriptions define state. Therefore we can toggle subscriptions and assign new ones.
149 | This method could also be made static'aly available and passed around for generic subscription management. However,
150 | within the same component tree, a state-handler should be passed around instead.
151 | */
152 | toggleSubscription(publication) {
153 | let subscription = this.state.subscription[publication];
154 |
155 | if (subscription.ready()) {
156 | subscription.stop()
157 | } else {
158 | this.setState({subscription: {tasks: Meteor.subscribe(publication)}})
159 | }
160 | }
161 |
162 | /* Meteor, React
163 | An object array is simply returned from our collection. With TrackerReact, any changes to the underlying will lead
164 | to an automatic component re-render with a new return value -> virtual dom diffing -> dom update.
165 | */
166 | //noinspection JSMethodCanBeStatic
167 | tasks() {
168 | return Tasks.find().fetch().reverse();
169 | }
170 |
171 | handleTasksInsert(e) {
172 | e.preventDefault();
173 |
174 | /* Meteor
175 | Client-side inserts - due to our allowed auth settings - will propagate from the client, to the server
176 | and to other clients automatically.
177 | */
178 | Tasks.insert({
179 | title: this.refs["todoTitle"].value || "No Title",
180 | text: this.refs["todoText"].value || "No Message Text"
181 | });
182 | }
183 |
184 | render() {
185 | return (
186 |
187 |
188 |
Reactive Data
189 |
Subscribe to Meteor Collections and
190 | see real-time updates. Open this
191 | page in another browser window and save a new todo message to the "todos" collection.
207 | )
208 | }
209 | }
210 |
211 | /* React
212 | A generic react component can be simply used within a TrackerReact component.
213 | */
214 | class Task extends React.Component {
215 | render() {
216 | return (
217 |
218 |
{this.props.task.title}
219 |
{this.props.task.text}
220 |
221 | )
222 | }
223 | }
224 |
225 |
226 | /***********************************************************************************************************************
227 | *
228 | * [MethodShowcase]
229 | * This component uses Meteor Methods to invoke collection changes on the server side. Changes are
230 | * optimistically applied on the client until the server either confirms or discards the change (i.e. due to missing
231 | * authentication). Here for, the method needs to be both defined on the server and the client (stub simulation).
232 | *
233 | **********************************************************************************************************************/
234 |
235 | /* Meteor
236 | The relevant Methods and the "temperature" collection is defined in this file. It is in an external file, so that it
237 | can be easily imported on the server as well. See "/server/dataPublications.js".
238 | */
239 | import "/imports/dataTemperature";
240 |
241 | /* React
242 | We use a simple react component from npm to illustrate reactivity and optimistic updates.
243 | Source: https://www.npmjs.com/package/react-thermometer
244 | */
245 | import Thermometer from "react-thermometer";
246 |
247 | /* Meteor, React -> TrackerReact
248 | Here the mixin method is used (scroll to the end of the file). Real-time data-invalidation due to optimistic updates.
249 | -> "ReactMixin(MethodShowcase.prototype, TrackerReactMixin);"
250 | */
251 | import ReactMixin from 'react-mixin';
252 | import {TrackerReactMixin} from 'meteor/ultimatejs:tracker-react';
253 |
254 | class MethodShowcase extends React.Component {
255 |
256 | /* Meteor, React
257 | Same reasoning as before. See above [CollectionShowcase].
258 | */
259 | constructor() {
260 | super();
261 |
262 | this.state = {
263 | subscription: {
264 | temperature: Meteor.subscribe('temperature')
265 | }
266 | }
267 | }
268 |
269 | /* Meteor, React
270 | Same reasoning as before. See above [CollectionShowcase].
271 | */
272 | //noinspection JSUnusedGlobalSymbols
273 | componentWillUnmount() {
274 | this.state.subscription.temperature.stop();
275 | }
276 |
277 | /* Meteor
278 | FindOne returns the last document object added to the "temperature" collection. Note that we are returning a
279 | loading stub for when the data is not loaded yet. With TrackerReact, loading indicators can be implicit to data
280 | availability and is not limited to whole subscriptions.
281 | */
282 | //noinspection JSMethodCanBeStatic
283 | temperature() {
284 | return Temperature.findOne({}, {sort: {created: -1}}) || {current: 0, source: "loading"};
285 | }
286 |
287 | /* Meteor
288 | Before we inserted data directly into a client side collection which propagated itself to the server. Here, a
289 | Meteor Method is "called" to trigger an insert on the server. But the method is also available on the client,
290 | allowing for an optimistic update of data.
291 |
292 | In the end, the data inserted on the server slightly diverts with a different "source" property. As soon the
293 | server caught up, the optimistic data of the client is corrected by the data on the server (source changed from
294 | client to server). Despite any network latency.
295 |
296 | See "/server/dataPublications.js".
297 | */
298 | handleTemperatureReading(e) {
299 | e.preventDefault();
300 |
301 | let reading = this.refs["reading"].value;
302 | let delay = this.refs["delay"].value;
303 |
304 | if (reading >= 0 && reading <= 30) {
305 | Meteor.call("addMeasure", reading, delay);
306 | }
307 | }
308 |
309 | /* Meteor
310 | A Meteor Method that only runs on the server, without optimistic updates, changing the last reading
311 | on the server's mongo collection.
312 |
313 | See "/server/dataPublications.js".
314 | */
315 | //noinspection JSMethodCanBeStatic
316 | changeTemperature(amount) {
317 | Meteor.call("changeMeasure", amount);
318 | }
319 |
320 | render() {
321 | return (
322 |
323 |
324 |
Optimistic Updates
325 |
Make use of Meteor Methods. Simulate
326 | different network latencies below and observe how
327 | data changes are first applied client side, than confirmed server side.