Flux Example App with React & Meteor

I’ve recently implemented flux into my React Meteor workflow. It’s really helped improve debugging and maintainability. I’ve used this on parts of a production app with complex state and it has been working well!

I suspect this will not be as well received by current Meteor devs but I think that current React devs will find this natural and will see how nice Meteor can work to make their Flux reactive with an optimistic UI :slight_smile: (writing a blog post for that).

The gist with flux is your UI triggers ‘actions’. A dispatcher catches these actions and sends the payload to anyone listening (a data store or component). The stores manipulate their state based on the action. Components listen to stores and sync their data when a ‘store changed’ event is heard. That’s it!

The key is to have Tracker watch a collection and trigger an action when it changes. The store(s) can listen for this change and re-sync (which trigger changes in the UI).

At first it may seem like using flux with Meteor is going to cause a lot of overlap and extra work. However there isn’t much overlap. It also eliminates the ReactiveDataMixin plugin and allows you to have more control over re-renders.

  Tracker.autorun(computation => {
    var docs = Players.find({}).fetch();
    if (computation.firstRun) return; // ignore first empty run
    console.log('[Tracker] collection changed');
    this.CollectionActions.playersChanged(docs);
  });

This can be reduced down to a helper function. Pass in the collection to watch and a callback function to call when it changes.

watchForCollectionChanges(Players, Actions.playersChanged);

The data lifecycle looks like this:

  • render PlayerList component and listen to PlayerStore (empty array at this point)
  • subscribe to players subscription
  • tracker fires ‘collection changed’ event
  • dispatcher send event to stores listening
  • PlayerStore fetches Minimongo to refresh store, emit store changed
  • dispatcher sends event to listeners
  • PlayerList acts on ‘store changed’ event and fetches store state, passes to children,
  • UI gets updated with new data from parent (PlayerList)

All the data coming from flux and going to the components is non-reactive. However, because tracker updates the stores, all of the latency compensation and realtime updates still work like normal!

This repo shows how you can use Alt, a flavor of flux, with the leaderboard app. Note, the dispatcher is fired when an action returns, this reduces the boilerplate and complexity of vanilla flux (I may add a branch with that too if there’s enough interest).

9 Likes

Wow… crickets :laughing:

Here’s a nice example of using Alt’s time travel to rewind/replay your Meteor app’s state while debugging (or you could use it in prod as undo/redo).

You can even have a user send you their app state on an error!

Video Link

3 Likes

This is great, thanks a lot!

I started developing an app using meteorflux:dispatcher (following the structure used in https://github.com/meteorflux/cartflux) but ran into issues when using it with React (it doesn’t provide a solution for pushing changes to data down to the components, the author uses Blaze and relies on the reactivity of template helpers).

I’m going to try out your solution. The rewind/replay and snapshot functionality are really cool.

1 Like

@SkinnyGeek1010, this is great! I don’t get a chance to post much but I read a lot and have enjoyed many of your posts on react and flux.

As for this example, Alt looks pretty cool! I’ll have to give it a shot next time. I’ve used reflux in the past so it was easy enough to convert your example from Alt to Reflux.

Although it might be a little hard to read, I left the original code in comments for comparison.

1 Like

Awesome! Thanks!

Feel free to submit a PR if you’d like me to add a ‘reflux’ branch to the main repo.

Cheers! :beers:

Done. Not sure if I did it correctly though. Do I need to create the reflux branch first or can you take care of that?

Thanks!! I just created a reflux-example branch and merged it in and added it to the readme. :beers:

I might strip out the old Alt comments though since they can still refer to the master for comparison. Would that be easier for people to grok the reflux?

For those curious:

Reflux Example

Reflux docs

Yeah, I think cleaning it up would be a good thing. Thanks!

1 Like

Hi -

I am a noob in meteor world with some weight in node + reactJS and flux world. I was wondering , how can we include off the shelf available functionality like google oauth sign in in react views. having that in the basic example would be great. I would love to build that, but seems it is not as straightforward as it is in blaze world.

~saurshaz

If you have accounts-google in your app or package somewhere, you should be able to call Meteor.loginWithGoogle just like with Blaze. Something like:

var SomeComponent = React.createClass({
    render() {
        return (
            // stuff, like a inputs to collect login info
            <Button onClick={this.handleClick}/>
            // maybe more stuff
        )
    }
    handleClick() {
        Meteor.loginWithGoogle(/*login info and callback*/);
    }
});

This seems pretty hard at first but if you break it down it’s pretty easy :smiley:

First to make things simple, we can use the Blaze Accounts UI package to save ourselves some legwork. You can add that package and accounts-facebook to get the login buttons and facebook button. (also like @donaldaverill mentions you can just use the Meteor API to use your own login)

Then you can either render the login buttons directly into the header, or preferably with a blaze template helper:

// create a general helper to render blaze templates
this.BlazeTemplate = React.createClass({
  propTypes: {
    template:   React.PropTypes.any.isRequired,
    component:  React.PropTypes.any,
  },

  getDefaultProps() {
    return {
      component: 'div',
    };
  },

  componentDidMount() {
    this.view = Blaze.render(this.props.template, React.findDOMNode(this.refs.root));
  },

  componentWillUnmount() {
    Blaze.remove(this.view);
  },

  render() {
    var {component, template, ...props} = this.props;
    props.ref = 'root';

    return React.createElement(component, props);
  }
});

then just render them like this:

<BlazeTemplate template={Template.loginButtons} />

If you want your UI to update as you login and logout, you just need to track the users collection. Specifically you want to track the 'viewer' eg, the user that's using the app. The below is a bit of boilerplate to track a user (i'm hoping to make some [helpers for this soon](https://github.com/AdamBrodzinski/meteor-flux-helpers)!)
  // only run on the client, not needed for SSR
  if (Meteor.isClient) {
    Meteor.startup(function() {

      Tracker.autorun(function(computation) {
        var user = Meteor.user();

        if (computation.firstRun) {
          return;  // ignore first empty run
        }

        console.log('[Tracker] viewers user doc changed', user);
        CollectionActions.viewerChanged(user);
      });

    });
  }

I’ve made a ‘viewer store’ to track and keep state on the user using the app.

// Store for viewer's user object

class ViewerStore {
  constructor() {
    //this.bindActions(ViewerActions);
    this.bindListeners({
      onViewerChanged: CollectionActions.viewerChanged
    });
  }

  onViewerChanged() {
    this.setState({viewer: Meteor.user()});
    console.log("[ViewerStore] user changed");
  }
}

this.ViewerStore = alt.createStore(ViewerStore, 'ViewerStore');

Then any view can subscribe to the ViewerStore to make sure it re-renders when the user logs in/out or the user doc changes.

Hope this helps!

1 Like

Hey thanks Donald

Will read that. I was working on bringing a minimal redux app structure
that i have to play along with meteor. Had initial hesitations but the
community support is encouraging

~saurshaz

1 Like

Yeah the meteor community is great.

I’d be interested to see the minimal redux/meteor app code once you get it going!

2 Likes

That definitely is very very helpful. Thanks @SkinnyGeek1010 for that. BTW - most of the work buy you in github is what i am following as starting point, being a fan of flux architecture myself. :slight_smile: Thanks for this !!

~saurshaz

1 Like

How would you implement routing with Alt (or Flux in general)?

Well, I guess most route changes would be done though simple links, but what about redirects (e.g. redirect a user from ‘/login’ to ‘/’ after a successful login).

I’m assuming the redirect would be triggered by a “successful login” event dispatched from LoginActions. If so, where would you listen for this event? Would you create a RouterStore or something?

1 Like

This is a great question and I don’t really have a solid answer for you. I also couldn’t get an answer on SO or the React forums. However i’ve been using the NavigatorIOS router in React Native and a bit of FlowRouter so i’ve been tinkering with this for some weeks.

@arunoda was also looking into this area as well. I’m not sure if he came up with any additional information.

My general conclusion is that if you don’t care about abstracting the router out of your view and/or you don’t need to track the route history in your dispatcher, then I would just keep using it the normal way.

To elaborate on the second reason; you may want to call an action goToPage(...) so that it’s tracked in your dispatcher calls. With libraries like Alt you can serialize this history and send it back on an error (with TrackJS or similar). You can also use this dispatcher data for ‘time travel’ with Alt or similar flux libraries.

This brings up another issue. How can you trigger an action when a user clicks on a link? The easiest way to create a <Link page='home' />component for anchor elements and add a goToPage prop on a <Button /> element for buttons that go to a page. Both of these would just call the routing action on click.

Redirects can directly call AppActions.goToPage('home') instead.


[quote="coniel, post:15, topic:7416"] I'm assuming the redirect would be triggered by a "successful login" event dispatched from LoginActions. [/quote]

This sounds right. I think the general flow for an async login would go like this:

  • view calls AppActions.loginUser(data)
  • loginUser action calls meteor async login (doing in store is considered bad)
  • the login callback has two paths:
  • on error call AppActions.loginFailed which can handle the bad login,
  • on success calls AppActions.loginSuccessful

The view can listen for those events by registering with the AppActions store, or if that was getting too large an AuthAction/Store would not be unreasonable (and more fine grained). That way the login view can listen for an error and act upon it (as well as any other part of the app).

It depends. If you want to subscribe to the query params reactively I would create a RouterStore/Actions. You can call the FlowRouter API in the view of course but without the mixin it won’t auto update.

If so then either track the FlowRouter API and emit a changed action or in the callback (like a normal collection) or preferably in the action callback of the route just emit a RouterActions.changed event and pass in the data from FlowRouter. I’m looking into integrating an optional prop in Reaktor where you can pass in a changeAction={yourActionHere} and it would trigger any time the route data changes (actually just submitted an issue for that).

It’s also worth noting that in smaller apps just using regular links and doing all the login logic in the view(container) is just fine. Having the complexity only helps when you have lots of stuff going on in your app.

Does this answer your questions? I’m hoping to integrate Alt into React-ive Meteor this week sometime so that flux examples include routing and auth.

And the standard main question: hot code pushes
What you are using or going to use as persistent state container for stores ?
local storage, reactive dict, client side minimongo collections or there is some other way how to plug in our own surviving structure ?

1 Like

I haven’t got around to adding this yet but my plan was to do the following:

use the _onMigrate callback and take a snapshot of the app alt.takeSnapshot() to serialize all the stores into JSON and save them to a Session variable (or Reactive Dict if available).

  Meteor._reload.onMigrate("onMigrate", function() {
    var snap = alt.takeSnapshot();
    Session.set("app:currentSnapshot", snap);
    return [true]; // i think this is the syntax?
  });

and then in a client/_startup.js file bootstrap the app stores when starting up. This would emit change events for each store making the view re-fetch and re-render.

Meteor.startup(function(){
  // load data back into stores and fire 'change' event
  alt.bootstrap(Session.get("app:currentSnapshot"));
});

This allows you to revert to the same state once it re-loads. If you’re using any local state, then it’s going to be harder to save that. However you could do the same thing manually (prob. easier to keep it in a flux store).

If you’re using reflux you’re prob. out of luck, unless you write custom code to serialize data and re-hydrate on startup. There are also other flux implementations with timetravel/snapshots but Airbnb has been using Alt with good success so I have decided to stick with that.

Hi all!

Really love the idea of using Alt via Browserify to handle an app’s flux implementation.

I have been working on an all-packages app, and am wondering if anyone has implemented a solution that allows a single Alt instance to be used across multiple packages? My first attempts were unsuccesful, using a package like this:

Package.describe({
  name: 'app:alt',
  version: '0.0.1',
  summary: '',
  git: '',
  documentation: 'README.md'
});

// npm modules
Npm.depends({
  'externalify': '0.1.0',
  'alt': '0.17.1'
});

Package.onUse(function(api) {
  api.versionsFrom('1.1.0.3');
  api.use(['cosmos:browserify@0.4.0'], 'client');

  api.add_files([
    'browserify/client.browserify.js',
    'browserify/client.browserify.options.json',
    ],
    'client'
  );

  api.export('alt', ['client']);
});

And a browserify file like this:

Alt = require("alt");
alt = new Alt();

The first package that required this package (with ‘alt’ globally exposed) worked fine, but the second package bugged out with the errror: ‘alt is not defined’.

My short term solution has been to implement Alt via Browserify on a per-package basis, but would love to know if anyone else has run into this as well!

1 Like

Hi Reyen, thanks for trying it out!

That’s a really interesting problem. Are you trying to package Alt so that it has a hard package dependency? Would it be an issue to just infer that Alt global is defined?

If it’s a new app and you’re after scope isolation, my first thought would be to use @jedwardsWebpack boilerplate to use ES6 modules or @trusktr 's module package (which may need tweaking over time).

Do you find that the all-packages way creates too much boilerplate? I tried this briefly but couldn’t get past it. Perhaps a generator would have helped.

I wish I could offer more, let me know if you find a solution! :beers: