├── .versions
├── README.md
├── Tracker.js
├── main.js
└── package.js
/.versions:
--------------------------------------------------------------------------------
1 | babel-compiler@6.6.4
2 | babel-runtime@0.1.8
3 | ecmascript@0.4.3
4 | ecmascript-runtime@0.2.10
5 | meteor@1.1.14
6 | modules@0.6.1
7 | modules-runtime@0.6.3
8 | promise@0.6.7
9 | tracker@1.0.13
10 | ultimatejs:tracker-react@1.0.5
11 | underscore@1.0.8
12 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 | Current Version: `1.0.5`
3 | ##### Meteor 1.3
4 |
5 | ```
6 | meteor add ultimatejs:tracker-react
7 | ```
8 | ##### Meteor 1.2
9 |
10 | ```
11 | meteor add ultimatejs:tracker-react@0.0.6
12 | ```
13 | TrackerReact is an upgrade to what `ReactMeteorData` offers. Using `TrackerReact` instead you are no longer required to "freeze" all your reactivity in a single method. Any *reactive data sources* (e.g: `collection.find()` or `Session.get('foo')`) used in your `render` method or by methods called by your `render` method are automatically reactive! This replicates the standard helper experience from Meteor/Blaze. Enjoy!
14 |
15 | GOTCHA: You must call `.fetch()` on your cursors to trigger reactivity!!
16 |
17 | ## Usage
18 | From **Meteor v.1.3 and up**, react components can be made reactive either by using TrackerReact in a *Composition (inheritance)*, as *Mixin* or as *Decorator*.
19 |
20 | #### Example-App
21 | Clone & Read the Source: https://github.com/D1no/TrackerReact-Example
22 |
23 | 
24 | #### Profiling
25 | *Currently only under the `profiler` Branch and will be added with the final release of Meteor 1.3*
26 | Use the profiler argument `TrackerReact(React.Component, {profiler: true})` to see render times of a reactive components. Or set `this._profMode = {profiler: true}` within the `constructor()` function.
27 |
28 | #### Implementation
29 | **Composition** (wrapping a relevant React.Component in TrackerReact) is a clean, default alternative until Meteor supports decorators.
30 |
31 | ```jsx
32 | import React from 'react';
33 | import ReactDOM from 'react-dom';
34 |
35 | // TrackerReact is imported (default) with Meteor 1.3 new module system
36 | import TrackerReact from 'meteor/ultimatejs:tracker-react';
37 |
38 | // Get the Collection
39 | Tasks = new Mongo.Collection("tasks");
40 |
41 | // > React.Component is simply wrapped with TrackerReact
42 | class App extends TrackerReact(React.Component) {
43 |
44 | // Note: In ES6, constructor() === componentWillMount() in React ES5
45 | constructor() {
46 | super();
47 | this.state = {
48 | subscription: {
49 | tasks: Meteor.subscribe('tasks')
50 | }
51 | }
52 | }
53 |
54 | componentWillUnmount() {
55 | this.state.subscription.tasks.stop();
56 | }
57 |
58 | //tracker-based reactivity in action, no need for `getMeteorData`!
59 | tasks() {
60 | return Tasks.find({}).fetch(); //fetch must be called to trigger reactivity
61 | },
62 |
63 |
64 | render() {
65 | return (
66 |
67 |
68 | Todo List - {Session.get('title')}
69 |
70 |
71 |
72 | {this.tasks().map((task) => {
73 | return ;
74 | })}
75 |
76 |
77 | );
78 | }
79 | });
80 | ```
81 |
82 | Same is possible **as Mixin** (ES6 example)
83 |
84 | ```jsx
85 | import React from 'react';
86 | import ReactDOM from 'react-dom';
87 |
88 | // Use ReactMixin from npm
89 | import ReactMixin from 'react-mixin';
90 |
91 | // > Make sure to import the TrackerReactMixin export
92 | import {TrackerReactMixin} from 'meteor/ultimatejs:tracker-react';
93 |
94 | // Get the Collection
95 | Tasks = new Mongo.Collection("tasks");
96 |
97 | class App extends React.Component {
98 |
99 | // (...)
100 |
101 | });
102 | // > Using ReactMixin
103 | ReactMixin(App.prototype, TrackerReactMixin);
104 | ```
105 |
106 | Same example **as Decorator** (ES6/ES7 Example).
107 | Cleanest solution: Requires support for decorators either setting babel to experimental or TypeScript with experimental ES7 features turned on.
108 |
109 | ```jsx
110 | import React from 'react';
111 | import ReactDOM from 'react-dom';
112 |
113 | // > Make sure to import the TrackerReactMixin export
114 | import {TrackerReactMixin} from 'meteor/ultimatejs:tracker-react';
115 |
116 | // Get the Collection
117 | Tasks = new Mongo.Collection("tasks");
118 |
119 | // > Assign as Decorator
120 | @TrackerReactMixin
121 | class App extends React.Component {
122 |
123 | // (...)
124 | ```
125 |
126 | #### Old Example: Meteor 1.2
127 | Package version: `ultimatejs:tracker-react@0.0.6`
128 | ```jsx
129 | App = React.createClass({
130 | mixins: [TrackerReact],
131 |
132 | //tracker-based reactivity in action, no need for `getMeteorData`!
133 | tasks() {
134 | return Tasks.find({}).fetch(); //fetch must be called to trigger reactivity
135 | },
136 |
137 |
138 | render() {
139 | return (
140 |
141 |
142 | Todo List - {Session.get('title')}
143 |
144 |
145 |
146 | {this.tasks().map((task) => {
147 | return ;
148 | })}
149 |
150 |
151 | );
152 | }
153 | });
154 | ```
155 |
--------------------------------------------------------------------------------
/Tracker.js:
--------------------------------------------------------------------------------
1 | // Also available as a global
2 | import {Tracker} from 'meteor/tracker';
3 |
4 | /**
5 | * Create "one-time" reactive computations with Tracker
6 | * @param name {string} Component Reactive Data Property for Computation
7 | * @param context {*} Target Component Instance
8 | * @param dataFunc {*} Data Context
9 | * @param updateFunc {*} Component ForceUpdate Method - To re-trigger render function
10 | * @returns {*} Symbol(react.element) - Result data-element composition
11 | */
12 | Tracker.once = function (name, context, dataFunc, updateFunc) {
13 | let data;
14 |
15 | // Stop it just in case the autorun never re-ran
16 | if (context[name] && !context[name].stopped) context[name].stop();
17 |
18 | // NOTE: we may want to run this code in `setTimeout(func, 0)` so it doesn't impact the rendering phase at all
19 | context[name] = Tracker.nonreactive(() => {
20 | return Tracker.autorun(c => {
21 | if (c.firstRun) {
22 |
23 | data = dataFunc.call(context);
24 |
25 | } else {
26 |
27 | // Stop autorun here so rendering "phase" doesn't have extra work of also stopping autoruns; likely not too
28 | // important though.
29 | if (context[name]) context[name].stop();
30 |
31 | // where `forceUpdate` will be called in above implementation
32 | updateFunc.call(context);
33 | }
34 | });
35 | });
36 |
37 | return data;
38 | };
39 |
40 | export default Tracker
--------------------------------------------------------------------------------
/main.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Tracker is available as a global variable but is extended for one time computations/invalidation.
3 | * Implementation: See ./Tracker.js
4 | */
5 | import Tracker from './Tracker';
6 |
7 | /**
8 | * autorunRender(): The magic behind this computation is it only ever runs once after each time `render` is called.
9 | * When it does run that 2nd time, it's used just to force an update. The reactive function it wraps isn't even called.
10 | * Then on the update, the cycle repeats, and the computation is stopped, and a new one is made.
11 | *
12 | * Also, because the autorun is recreated on all React-triggered re-renders, any new code-paths possibly
13 | * taken in `render` will automatically begin tracking reactive dependencies, thereby MERGING both models of reactivity:
14 | * Meteor's various reactive data sources AND React's functional + unidirectional re-running of
15 | * everything in component branches with state changes.
16 | */
17 |
18 |
19 | /**
20 | * Default. Provides a react component for inheritance as a clean alternative to mixins.
21 | * Implementation:
22 | * "class MyApp extends TrackerReact(React.Component) { (...)"
23 | * @param Component {*} React Component
24 | */
25 | export default TrackerReact = function (Component, opt) {
26 | // No reactive computations needed for Server Side Rendering
27 | if (Meteor.isServer) return Component;
28 |
29 | class TrackerReactComponent extends Component {
30 |
31 | constructor(...args) {
32 | super(...args);
33 |
34 | /*
35 | Overloading the constructors `componentWillUnmount` method to ensure that computations are stopped and a
36 | forceUpdate prevented, without overwriting the prototype. This is a potential bug, as of React 14.7 the
37 | componentWillUnmount() method does not fire, if the top level component has one. It gets overwritten. This
38 | implementation is however similar to what a transpiler would do anyway.
39 |
40 | GitHub Issue: https://github.com/facebook/react/issues/6162
41 | */
42 | if (!this.constructor.prototype._isExtended) {
43 | this.constructor.prototype._isExtended = true;
44 | let superComponentWillUnmount = this.constructor.prototype.componentWillUnmount;
45 |
46 | this.constructor.prototype.componentWillUnmount = function (...args) {
47 | if (superComponentWillUnmount) {
48 | superComponentWillUnmount.call(this, ...args);
49 | }
50 |
51 | this._renderComputation.stop();
52 | this._renderComputation = null;
53 | };
54 | }
55 |
56 | this.autorunRender();
57 | }
58 |
59 | autorunRender() {
60 | let oldRender = this.render;
61 |
62 | this.render = () => {
63 | // Simple method we can offer in the `Meteor.Component` API
64 | return this.autorunOnce('_renderComputation', oldRender);
65 | };
66 | }
67 |
68 | autorunOnce(name, dataFunc) {
69 | return Tracker.once(name, this, dataFunc, this.forceUpdate);
70 | }
71 | }
72 |
73 | return TrackerReactComponent;
74 | };
75 |
76 |
77 | /**
78 | * Mixin. Use with ES7 / TypeScript Decorator or Mixin-Module.
79 | * Implementation:
80 | * "@TrackerReactMixin
81 | * class MyApp extends React.Component { (...)"
82 | * @type {{componentWillMount: (function()), componentWillUnmount: (function()), autorunRender: (function()),
83 | * autorunOnce: (function(*=, *=))}}
84 | */
85 | export const TrackerReactMixin = {
86 | componentWillMount() {
87 | // No reactive computations needed for Server Side Rendering
88 | if (Meteor.isServer) return;
89 |
90 | this.autorunRender();
91 | },
92 | componentWillUnmount() {
93 | // No reactive computations needed for Server Side Rendering
94 | if (Meteor.isServer) return;
95 |
96 | this._renderComputation.stop();
97 | this._renderComputation = null;
98 | },
99 | autorunRender() {
100 | let oldRender = this.render;
101 |
102 | this.render = () => {
103 | // Simple method we can offer in the `Meteor.Component` API
104 | return this.autorunOnce('_renderComputation', oldRender);
105 | };
106 | },
107 | autorunOnce(name, dataFunc) {
108 | return Tracker.once(name, this, dataFunc, this.forceUpdate);
109 | }
110 | };
111 |
--------------------------------------------------------------------------------
/package.js:
--------------------------------------------------------------------------------
1 | Package.describe({
2 | name: "ultimatejs:tracker-react",
3 | summary: "No-Config reactive React Components with Meteor. Apply as composition, mixin or decorator.",
4 | version: '1.0.5',
5 | documentation: 'README.md',
6 | git: 'https://github.com/ultimatejs/tracker-react'
7 | });
8 |
9 | Package.onUse(function (api) {
10 | api.versionsFrom('METEOR@1.3');
11 | api.use('tracker');
12 | api.use('underscore');
13 | api.use('ecmascript');
14 |
15 | api.mainModule('main.js');
16 | });
--------------------------------------------------------------------------------