├── .versions ├── CHANGELOG.md ├── LICENSE.txt ├── README.md ├── examples └── leaderboard │ ├── .meteor │ ├── .finished-upgraders │ ├── .gitignore │ ├── .id │ ├── packages │ ├── platforms │ ├── release │ └── versions │ ├── README.md │ ├── leaderboard.css │ ├── leaderboard.html │ └── leaderboard.jsx ├── package.js └── src ├── DDPMixin.js └── ReactiveMixin.js /.versions: -------------------------------------------------------------------------------- 1 | grove:react@0.3.0 2 | meteor@1.1.6 3 | reactive-var@1.0.5 4 | tracker@1.0.7 5 | underscore@1.0.3 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### v0.3.0 2 | * No longer includes React vendor files 3 | 4 | ### v0.2.0 5 | * Subscription setup moved to componentWillMount. Now have access to this.state 6 | within the `subscriptions` method. 7 | * React addons back in 8 | 9 | ### v0.1.4 10 | Updating React to v0.13.3 11 | 12 | ### v0.1.3 13 | Add true state for reactivevar when no subs 14 | 15 | ### v0.1.2 16 | * Exporting React on the server 17 | 18 | ### v0.1.1 19 | * Leaderboard example added 20 | 21 | ### v0.1.0 22 | Initial release -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Grove Labs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #DEPRECATED 2 | This package has been deprecated in favor of the official Meteor React package, [`react-meteor-data`](https://react-in-meteor.readthedocs.org/en/latest/meteor-data/). 3 | 4 | # Meteor & React 5 | This a [Meteor](https://meteor.com) package that includes 2 React mixins that enable binding reactive data sources and DDP subscriptions to a React Component. 6 | 7 | *If you're looking for a JSX compiler see [`grigio:babel`](https://github.com/grigio/meteor-babel)* 8 | 9 | ## Table of Contents 10 | 1. [Usage](#usage) 11 | 2. [ReactiveMixin](#reactivemixin) 12 | 3. [DDPMixin](#ddpmixin) 13 | 2. [Loading React](#loading-react) 14 | 2. [How it works](#how-it-works) 15 | 3. [Future Fork](#future-work) 16 | 17 | ## Usage 18 | ### Installation 19 | 20 | ``` 21 | meteor add grove:react 22 | ``` 23 | 24 | ### ReactiveMixin 25 | This mixin provides a way of binding reactive data sources to a React 26 | component. Components that use this mixin should implement a method 27 | named `getReactiveState` and return an object from that method, much 28 | like the standard `getInitialState`. Within the `getReactiveState` 29 | function you must make sure to call on [reactive data sources](http://docs.meteor.com/#/full/reactivity) or else the method won't rerun. 30 | 31 | 32 | 33 | #### Example 34 | ```js 35 | Bookface = React.createClass({ 36 | mixins: [ ReactiveMixin ], 37 | 38 | getReactiveState: function() { 39 | return { 40 | friends: Friends.find().fetch(), 41 | loggedIn: !!Meteor.user() 42 | } 43 | }, 44 | 45 | render: function() { 46 | if (this.state.loggedIn) { 47 | if (this.state.friends.length > 0) { 48 | return

You've got friends!

49 | } 50 | return

Forever alone...

; 51 | } 52 | return

Please log in

53 | } 54 | }); 55 | ``` 56 | 57 | ### DDPMixin 58 | This mixin provides a way of binding DDP subscriptions to a React component. Components that use this mixin should implement a method named `subscriptions` and return either a single [subscription handle](http://docs.meteor.com/#/full/meteor_subscribe) or an array of subscription handles. You can then call `subsReady()` within `getReactiveState` to reactively wait on them, or check `this.state.subsReady` from within the render function to see if they're ready. (You can also call `this.subsReady()` from within `render` but it follows React convention more closely to use the component's state). 59 | 60 | Make sure to include the DDPMixin _before_ ReactiveMixin for the component. As stated in the [Reusable Component docs](https://facebook.github.io/react/docs/reusable-components.html#mixins), _"methods defined on mixins run in the order mixins were listed, followed by... the component."_ DDPMixin must run first and define the `subsReady` function before it can be called within `getReactiveState`. 61 | 62 | An interesting use case might be to have a component that makes its own DDP connection and uses that instead of the default Meteor connection. `Meteor.subscribe` is just a [bound wrapper around a DDP connection](https://github.com/meteor/meteor/blob/devel/packages/ddp/client_convenience.js#L45-L56) 63 | 64 | A `SubsManager` object from [`meteorhacks:subs-manager`](https://github.com/meteorhacks/subs-manager) could also be used instead of `Meteor.subscribe`. 65 | 66 | #### Example 67 | 68 | ```js 69 | Post = React.createClass({ 70 | mixins: [ DDPMixin, ReactiveMixin ], 71 | 72 | getInitialState: function () { 73 | return { 74 | post: null 75 | }; 76 | }, 77 | 78 | getReactiveState: function() { 79 | if ( this.subsReady() ) { 80 | var p = Posts.findOne( Session.get('currentPostId') ); 81 | return { post: p }; 82 | } 83 | }, 84 | 85 | subscriptions: function() { 86 | return Meteor.subscribe('singlePost', Session.get('currentPost') ); 87 | }, 88 | 89 | // or if you want to wait on multiple subscriptions 90 | subscriptions: function() { 91 | return [ 92 | Meteor.subscribe('singlePost', Session.get('currentPostId') ), 93 | Meteor.subscribe('postComments', Session.get('currentPostId') ) 94 | ]; 95 | }, 96 | 97 | render: function() { 98 | if ( this.state.post ) { // or this.state.subsReady, but if the publication 99 | return ( // was empty then this.state.post is undefined 100 |
101 |

{this.state.post}

102 |
103 | ); 104 | } 105 | return

Loading...

; 106 | } 107 | }); 108 | ``` 109 | For more modularity you could use the params from your Router instead of `Session.get` 110 | 111 | ## Loading React 112 | It's recommended to use [`cosmos:browserify`](https://github.com/elidoran/cosmos-browserify/) to get the React library itself. To do so correctly, you'll want to create a local package, `require` what you want, and then explicitly export them. You want to use a local package so that it gets loaded in before your application. For example, if you want to use React with addons and React Router: 113 | 114 | ```js 115 | // packages/client-deps/package.js 116 | Package.describe({ 117 | name: 'client-deps', 118 | }); 119 | 120 | Npm.depends({ 121 | "react" : "0.13.3", 122 | "react-router" : "0.13.3" 123 | }); 124 | 125 | Package.onUse(function(api) { 126 | api.use(['cosmos:browserify@0.2.0']); 127 | api.addFiles(['browserify.js']); 128 | api.export(['React', 'ReactRouter']); 129 | }); 130 | ``` 131 | 132 | ```js 133 | // packages/client-deps/browserify.js 134 | React = require('react/addons'); 135 | ReactRouter = require('react-router'); 136 | ``` 137 | 138 | `React` and `ReactRouter` will then be exposed at the global scope to both the client and server. 139 | 140 | **A note on using the production version of React**: The difference between the development and production versions of React is more than just minification. There are warnings that are removed and optimizations made. To remove these warnings, make sure that your `NODE_ENV` environment variable is set to `"production"` when building your app. The [envify](https://www.npmjs.com/package/envify) transform present in cosmos:browserify will replace `process.env.NODE_ENV` with `"production"` throughout the library, and then when Meteor runs [UglifyJS](https://github.com/mishoo/UglifyJS2) it'll eliminate the now-dead code. 141 | 142 | ## How it works 143 | ### Reactive Subscriptions 144 | All of the subscription handles that are returned from `subscriptions` have `.ready()` called on them, which is a reactive data source. Every time a new subscription becomes ready it will check them all again. Once they are all ready, a `ReactiveVar` is set to true. The component method `this.subsReady()` is actually a bound `get` call on a ReactiveVar. 145 | 146 | ```js 147 | var subsReady = new ReactiveVar(false); 148 | this.subsReady = subsReady.get.bind(subsReady); 149 | ``` 150 | 151 | When called from within `getReactiveState`, it sets up a dependency on that method's `Tracker.autorun` even as a bound function (which is pretty awesome). 152 | 153 | ## Future Work 154 | ### Server-side 155 | The package loads React onto server and client, but the mixins are not supported on the server since Tracker and `Meteor.subscribe` are not supported on the server. Thoughts and pull requests welcome. 156 | -------------------------------------------------------------------------------- /examples/leaderboard/.meteor/.finished-upgraders: -------------------------------------------------------------------------------- 1 | # This file contains information which helps Meteor properly upgrade your 2 | # app when you run 'meteor update'. You should check it into version control 3 | # with your project. 4 | 5 | notices-for-0.9.0 6 | notices-for-0.9.1 7 | 0.9.4-platform-file 8 | notices-for-facebook-graph-api-2 9 | -------------------------------------------------------------------------------- /examples/leaderboard/.meteor/.gitignore: -------------------------------------------------------------------------------- 1 | local 2 | -------------------------------------------------------------------------------- /examples/leaderboard/.meteor/.id: -------------------------------------------------------------------------------- 1 | # This file contains a token that is unique to your project. 2 | # Check it into your repository along with the rest of this directory. 3 | # It can be used for purposes such as: 4 | # - ensuring you don't accidentally deploy one app on top of another 5 | # - providing package authors with aggregated statistics 6 | 7 | 18jw9601dypnuo1hstt7l 8 | -------------------------------------------------------------------------------- /examples/leaderboard/.meteor/packages: -------------------------------------------------------------------------------- 1 | # Meteor packages used by this project, one per line. 2 | # Check this file (and the other files in this directory) into your repository. 3 | # 4 | # 'meteor add' and 'meteor remove' will edit this file for you, 5 | # but you can also edit it by hand. 6 | 7 | meteor-platform 8 | insecure 9 | grigio:babel 10 | grove:react 11 | accounts-ui 12 | accounts-password 13 | -------------------------------------------------------------------------------- /examples/leaderboard/.meteor/platforms: -------------------------------------------------------------------------------- 1 | server 2 | browser 3 | -------------------------------------------------------------------------------- /examples/leaderboard/.meteor/release: -------------------------------------------------------------------------------- 1 | METEOR@1.1.0.1 2 | -------------------------------------------------------------------------------- /examples/leaderboard/.meteor/versions: -------------------------------------------------------------------------------- 1 | accounts-base@1.2.0 2 | accounts-password@1.1.1 3 | accounts-ui@1.1.5 4 | accounts-ui-unstyled@1.1.7 5 | autoupdate@1.2.1 6 | base64@1.0.3 7 | binary-heap@1.0.3 8 | blaze@2.1.2 9 | blaze-tools@1.0.3 10 | boilerplate-generator@1.0.3 11 | callback-hook@1.0.3 12 | check@1.0.5 13 | ddp@1.1.0 14 | deps@1.0.7 15 | ejson@1.0.6 16 | email@1.0.6 17 | fastclick@1.0.3 18 | geojson-utils@1.0.3 19 | grigio:babel@0.1.0 20 | grove:react@0.1.1 21 | html-tools@1.0.4 22 | htmljs@1.0.4 23 | http@1.1.0 24 | id-map@1.0.3 25 | insecure@1.0.3 26 | jquery@1.11.3_2 27 | json@1.0.3 28 | launch-screen@1.0.2 29 | less@1.0.14 30 | livedata@1.0.13 31 | localstorage@1.0.3 32 | logging@1.0.7 33 | meteor@1.1.6 34 | meteor-platform@1.2.2 35 | minifiers@1.1.5 36 | minimongo@1.0.8 37 | mobile-status-bar@1.0.3 38 | mongo@1.1.0 39 | npm-bcrypt@0.7.8_2 40 | observe-sequence@1.0.6 41 | ordered-dict@1.0.3 42 | random@1.0.3 43 | reactive-dict@1.1.0 44 | reactive-var@1.0.5 45 | reload@1.1.3 46 | retry@1.0.3 47 | routepolicy@1.0.5 48 | service-configuration@1.0.4 49 | session@1.1.0 50 | sha@1.0.3 51 | spacebars@1.0.6 52 | spacebars-compiler@1.0.6 53 | srp@1.0.3 54 | templating@1.1.1 55 | tracker@1.0.7 56 | ui@1.0.6 57 | underscore@1.0.3 58 | url@1.0.4 59 | webapp@1.2.0 60 | webapp-hashing@1.0.3 61 | -------------------------------------------------------------------------------- /examples/leaderboard/README.md: -------------------------------------------------------------------------------- 1 | ## Leaderboard with `grove:react` + `grigio:babel` -------------------------------------------------------------------------------- /examples/leaderboard/leaderboard.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; 3 | font-weight: 200; 4 | margin: 50px 0; 5 | padding: 0; 6 | -webkit-user-select: none; 7 | -khtml-user-select: none; 8 | -moz-user-select: none; 9 | -o-user-select: none; 10 | user-select: none; 11 | } 12 | 13 | #outer { 14 | width: 600px; 15 | margin: 0 auto; 16 | } 17 | 18 | .player { 19 | cursor: pointer; 20 | padding: 5px; 21 | } 22 | 23 | .player .name { 24 | display: inline-block; 25 | width: 300px; 26 | font-size: 1.75em; 27 | } 28 | 29 | .player .score { 30 | display: inline-block; 31 | width: 100px; 32 | text-align: right; 33 | font-size: 2em; 34 | font-weight: bold; 35 | color: #777; 36 | } 37 | 38 | .player.selected { 39 | background-color: yellow; 40 | } 41 | 42 | .player.selected .score { 43 | color: black; 44 | } 45 | 46 | .details, .none { 47 | font-weight: bold; 48 | font-size: 2em; 49 | border-style: dashed none none none; 50 | border-color: #ccc; 51 | border-width: 4px; 52 | margin: 50px 10px; 53 | padding: 10px 0px; 54 | } 55 | 56 | .none { 57 | color: #777; 58 | } 59 | 60 | .inc { 61 | cursor: pointer; 62 | } 63 | -------------------------------------------------------------------------------- /examples/leaderboard/leaderboard.html: -------------------------------------------------------------------------------- 1 | 2 | Leaderboard react + meteor 3 | 4 | 5 | 6 |
7 | {{> loginButtons}} 8 |
9 |
10 |
11 | 12 | -------------------------------------------------------------------------------- /examples/leaderboard/leaderboard.jsx: -------------------------------------------------------------------------------- 1 | // both 2 | Players = new Meteor.Collection("players"); 3 | 4 | Meteor.methods({ 5 | addPoints: function(userId, points) { 6 | Players.update(userId, { $inc: { score: +points } }); 7 | } 8 | }); 9 | 10 | // client-side definition of and initialization 11 | if (Meteor.isClient) { 12 | 13 | var Leaderboard = React.createClass({ 14 | mixins: [ DDPMixin, ReactiveMixin ], 15 | 16 | subscriptions: function() { 17 | return Meteor.subscribe("players"); 18 | }, 19 | 20 | getReactiveState: function() { 21 | if ( this.subsReady() ) { 22 | var selectedPlayer = Players.findOne(Session.get("selected_player")); 23 | return { 24 | user: Meteor.user() && Meteor.user().emails[0].address, 25 | players: Players.find({}, {sort: {score: -1, name: 1}}).fetch(), 26 | selectedPlayer: selectedPlayer, 27 | selectedName: selectedPlayer && selectedPlayer.name 28 | }; 29 | } 30 | }, 31 | 32 | addFivePoints: function() { 33 | Meteor.call("addPoints", Session.get("selected_player"), 5); 34 | }, 35 | 36 | selectPlayer: function(id) { 37 | Session.set("selected_player", id); 38 | }, 39 | 40 | renderPlayer: function(model) { 41 | var _id = this.state.selectedPlayer && this.state.selectedPlayer._id; 42 | 43 | return ; 50 | }, 51 | 52 | render: function() { 53 | if (! this.state.players) return

loading..

54 | 55 | var inputOrMessage; 56 | 57 | if (this.state.selectedName) { 58 | inputOrMessage =
59 |
{this.state.selectedName}
60 | 66 |
67 | } else { 68 | inputOrMessage =
69 | Click a player to select 70 |
71 | } 72 | 73 | return ( 74 |
75 | { this.state.players.map(this.renderPlayer) } 76 | { inputOrMessage } 77 | You are {this.state.user || 'not logged in :('} 78 |
79 | ) 80 | 81 | } 82 | }); 83 | 84 | // Alternative to `React.createClass({...})` but no ES6 mixins support 85 | // see: https://facebook.github.io/react/docs/reusable-components.html#no-mixins 86 | class Player extends React.Component { 87 | constructor(props) { 88 | super(props); 89 | } 90 | 91 | render() { 92 | var { name, score, ...rest } = this.props; 93 | var classString = `player ${rest.className}`; 94 | return
95 | {name} 96 | {score} 97 |
; 98 | } 99 | } 100 | 101 | // Attach to a document id. 102 | Meteor.startup(function (argument) { 103 | React.render(, document.getElementById('leaderboard_placeholder')); 104 | }); 105 | 106 | } // client 107 | 108 | // On server startup, create some players if the database is empty. 109 | if (Meteor.isServer) { 110 | Meteor.startup(function () { 111 | if (Players.find().count() === 0) { 112 | var names = ["Ada Lovelace", 113 | "Grace Hopper", 114 | "Marie Curie", 115 | "Carl Friedrich Gauss", 116 | "Nikola Tesla", 117 | "Claude Shannon"]; 118 | for (var i = 0; i < names.length; i++) { 119 | Players.insert({ 120 | name: names[i], 121 | score: Math.floor(Random.fraction()*10)*5 122 | }); 123 | } 124 | } 125 | }); 126 | 127 | Meteor.publish("players", function() { 128 | return Players.find(); 129 | }); 130 | } 131 | -------------------------------------------------------------------------------- /package.js: -------------------------------------------------------------------------------- 1 | Package.describe({ 2 | name: "grove:react", 3 | version: "0.3.0", 4 | summary: "React for Meteor - vendor files and essential mixins", 5 | git: "https://github.com/grovelabs/meteor-react/" 6 | }); 7 | 8 | Package.onUse( function(api) { 9 | api.use([ 10 | 'tracker@1.0.7', 11 | 'reactive-var@1.0.5', 12 | 'underscore@1.0.3' 13 | ], 'client'); 14 | 15 | api.addFiles([ 16 | 'src/ReactiveMixin.js', 17 | 'src/DDPMixin.js' 18 | ], 'client'); 19 | 20 | api.export([ 21 | 'ReactiveMixin', 22 | 'DDPMixin' 23 | ], 'client'); 24 | 25 | }); -------------------------------------------------------------------------------- /src/DDPMixin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * React Mixin for binding reactive data sources and DDP subscriptions 3 | * to the state of a component using Tracker. 4 | * 5 | * Components using this mixin should implement `subscriptions`, returning 6 | * either a single subscription handle, an array of subscription handles, 7 | * or nothing. These subscriptions handles come from sources such as 8 | * `Meteor.subscribe` 9 | * 10 | * Components using this mixin have access to an additional field on the state, 11 | * `subsReady`, and a function on the component, also `subsReady`. The 12 | * difference between the two is that the `subsReady` function is reactive. 13 | * You want to use that in `getReactiveState` -- it sets up a dependency 14 | * within the implicit Tracker.autorun. You can use `subsReady` within a 15 | * components `render`, but it follows React convention more closely to use the 16 | * state variable instead. 17 | * 18 | * `this.state.subsReady` {Boolean} represents state of the declared subs 19 | * `this.subsReady` {Function} reactive method also representing state of subs 20 | * 21 | * 22 | */ 23 | DDPMixin = { 24 | 25 | getInitialState: function() { 26 | var self = this; 27 | if ( self.subscriptions ) { 28 | // Setting up Tracker state 29 | var subsReady = new ReactiveVar(false); 30 | self._subsReadyVar = subsReady; 31 | // The reactive method to call in getReactiveState 32 | self.subsReady = subsReady.get.bind(subsReady); 33 | 34 | // Setting up React state 35 | return { 36 | subsReady: false 37 | }; 38 | } 39 | }, 40 | 41 | componentWillMount: function() { 42 | var self = this; 43 | self._subsComputation = Tracker.autorun( function(computation) { 44 | var subsReady; 45 | // If you call Meteor.subscribe within Tracker.autorun, the 46 | // subscription will be automatically cancelled when the computation 47 | // is invalidated or stopped; it's not necessary to call stop on 48 | // subscriptions made from inside autorun. However, if the next 49 | // iteration of your run function subscribes to the same record set 50 | // (same name and parameters), Meteor is smart enough to skip a 51 | // wasteful unsubscribe/resubscribe 52 | var subs = self.subscriptions(); 53 | // assuming it's either undefined or DDP.subscribe handles 54 | if (typeof subs !== 'undefined') { 55 | subs = [].concat(subs); // make it an array 56 | subsReady = _.every(subs, function(sub) { return sub.ready(); }); 57 | // The .ready() call is the reactive data source 58 | } else { 59 | // True if there are no subs, subscriptions() returned nothing 60 | subsReady = true; 61 | } 62 | 63 | self._subsReadyVar.set(subsReady); // Tracker 64 | self.setState({ // React 65 | subsReady: subsReady 66 | }); 67 | }); 68 | }, 69 | 70 | componentWillUnmount: function() { 71 | if ( this._subsComputation ) { 72 | this._subsComputation.stop(); 73 | this._subsComputation = null; 74 | } 75 | } 76 | 77 | }; -------------------------------------------------------------------------------- /src/ReactiveMixin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This mixin provides a way of binding reactive data sources to React 3 | * components. Components that use this mixin should implement a method 4 | * named `getReactiveState` and return an object from that method, much 5 | * like the standard `getInitialState`. Within the `getReactiveState` 6 | * function you must make sure to call on [reactive data sources](http://docs.meteor.com/#/full/reactivity) 7 | * or else the method won't rerun. See DDPMixin if you're trying to reactively 8 | * wait on DDP subscriptions to be ready 9 | * 10 | * If the subscriptions are ready immediately the `subsReady` state will be 11 | * updated synchronously, reflecting that state at `componentWillMount` 12 | */ 13 | ReactiveMixin = { 14 | 15 | getInitialState: function() { 16 | var self = this; 17 | if ( self.getReactiveState ) { 18 | var initState = {}; 19 | // Log a console warning if mixins are in the wrong order 20 | if (this.subscriptions && typeof this.subsReady === 'undefined' ) { 21 | console.warn('Need to bring in DDPMixin before ReactiveMixin to define the subscription state'); 22 | } 23 | self._reactiveStateComputation = Tracker.autorun( function(computation) { 24 | // Something in getReactiveState MUST be a reactive data source 25 | // in order for rerun 26 | var reactiveState = self.getReactiveState(); 27 | if ( typeof reactiveState !== 'undefined' ) { 28 | if (computation.firstRun) { 29 | initState = reactiveState; 30 | } else if ( self.isMounted() ) { 31 | // can't call setState until component is mounted 32 | self.setState(reactiveState); 33 | } else { // it's not mounted and we need to wait to `setState` 34 | Tracker.afterFlush(function () { 35 | self.setState(reactiveState); // set async 36 | }); 37 | } 38 | } 39 | }); 40 | return initState; 41 | } 42 | }, 43 | 44 | componentWillUnmount: function() { 45 | if (this._reactiveStateComputation) { 46 | this._reactiveStateComputation.stop(); 47 | this._reactiveStateComputation = null; 48 | } 49 | } 50 | 51 | }; --------------------------------------------------------------------------------