├── .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 | ![image](https://cloud.githubusercontent.com/assets/2397125/13386784/90369cda-deb0-11e5-8900-f13660467cd1.png) 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 | ![trackerreact-demo](https://cloud.githubusercontent.com/assets/2397125/13449628/b715eee0-e02d-11e5-9e62-2397397836d5.gif) 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 | 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 | 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 | }); --------------------------------------------------------------------------------