GithubHelp home page GithubHelp logo

mcfly's Introduction

McFly

Flux Architecture Made Easy

What is McFly?

When writing ReactJS apps, it is enormously helpful to use Facebook's Flux architecture. It truly complements ReactJS' unidirectional data flow model. Facebook's Flux library provides a Dispatcher, and some examples of how to write Actions and Stores. However, there are no helpers for Action & Store creation, and Stores require 3rd party eventing.

McFly is a library that provides all 3 components of Flux architecture, using Facebook's Dispatcher, and providing factories for Actions & Stores.

Demo

Check out this JSFiddle Demo to see how McFly can work for you:

http://jsfiddle.net/6rauuetb/

Download

McFly can be downloaded from:

http://kenwheeler.github.io/mcfly/McFly.js

Dispatcher

McFly uses Facebook Flux's dispatcher. When McFly is instantiated, a single dispatcher instance is created and can be accessed like shown below:

var mcFly = new McFly();

return mcFly.dispatcher;

In fact, all created Actions & Stores are also stored on the McFly object as actions and stores respectively.

Stores

McFly has a createStore helper method that creates an instance of a Store. Store instances have been merged with EventEmitter and come with emitChange, addChangeListener and removeChangeListener methods built in.

When a store is created, its methods parameter specifies what public methods should be added to the Store object. Every store is automatically registered with the Dispatcher and the dispatcherID is stored on the Store object itself, for use in waitFor methods.

Creating a store with McFly looks like this:

var _todos = [];

function addTodo(text) {
  _todos.push(text);
}

var TodoStore = mcFly.createStore({

getTodos: function() {
  return _todos;
}

}, function(payload){
  var needsUpdate = false;

  switch(payload.actionType) {
  case 'ADD_TODO':
    addTodo(payload.text);
    needsUpdate = true;
    break;
  }

  if (needsUpdate) {
    TodoStore.emitChange();
  }

});

Use Dispatcher.waitFor if you need to ensure handlers from other stores run first.

var mcFly = new McFly();
var Dispatcher = mcFly.dispatcher;
var OtherStore = require('../stores/OtherStore');
var _todos = [];

function addTodo(text, someValue) {
  _todos.push({ text: text, someValue: someValue });
}

 ...

    case 'ADD_TODO':
      Dispatcher.waitFor([OtherStore.dispatcherID]);
      var someValue = OtherStore.getSomeValue();
      addTodo(payload.text, someValue);
      break;

 ...

Stores are also created a with a ReactJS component mixin that adds and removes store listeners that call a storeDidChange component method.

Adding Store eventing to your component is as easy as:

var TodoStore = require('../stores/TodoStore');

var TodoApp = React.createClass({

  mixins: [TodoStore.mixin],

  ...

Actions

McFly's createActions method creates an Action Creator object with the supplied singleton object. The supplied methods are inserted into a Dispatcher.dispatch call and returned with their original name, so that when you call these methods, the dispatch takes place automatically.

Adding actions to your app looks like this:

var mcFly = require('../controller/mcFly');

var TodoActions = mcFly.createActions({
  addTodo: function(text) {
    return {
      actionType: 'ADD_TODO',
      text: text
    }
  }
});

All actions methods return promise objects so that components can respond to long functions. The promise will be resolved with no parameters as information should travel through the dispatcher and stores. To reject the promise, return a falsy value from the action's method. The dispatcher will not be called if the returned value is falsy or has no actionType.

You can see an example of how to use this functionality here:

http://jsfiddle.net/thekenwheeler/32hgqsxt/

API

McFly

var McFly = require('mcfly');

var mcFly = new McFly();

createStore

/*
 * @param {object} methods - Public methods for Store instance
 * @param {function} callback - Callback method for Dispatcher dispatches
 * @return {object} - Returns instance of Store
 */

createActions

/**
 * @param {object} actions - Object with methods to create actions with
 * @constructor
 */

mcfly's People

Contributors

balanceiskey avatar bigblind avatar coryhouse avatar dbellizzi avatar jcperez-ch avatar kenwheeler avatar ncherro avatar oclbdk avatar thabti avatar tomatau avatar tracker1 avatar zgotsch avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

mcfly's Issues

What am I doing wrong with waitFor and dispatcherID?

Not sure if this is very McFly related but here we go, I have two stores and I do something like:

case ActionTypes.SOME_ACTION: //User clicks
     mcFly.dispatcher.waitFor([SecondStore.dispatcherID]);
     _elements = {};
     break;

And my other store, for the same action, it does:

case ActionTypes.SOME_ACTION: //User clicks
     Object.assign(_myElements, FirstStore.getElements());
     break;

In the component that I am using them, I require them like:

var SecondStore = require('../stores/SecondStore');
var FirstStore = require('../stores/FirstStore');

The error I am getting is:

Error: Invariant Violation: Dispatcher.waitFor(...): undefined does not map to a registered callback.

So basically, SecondStore.dispatcherID is undefined. But, if I require them like:

var FirstStore = require('../stores/FirstStore');
var SecondStore = require('../stores/SecondStore');

I get:

TypeError: undefined is not a function at Object.ID_2

Which tells me that FirstStore.getElements() is not defined.

What am I missing? I cannot access to FirstStore from SecondStore and to SecondStore from the FirstStore? Am I creating a circular dependency ? The thing is that if in the first example, instead of using mcFly.dispatcher.waitFor([SecondStore.dispatcherID]); I use the id directly mcFly.dispatcher.waitFor(["ID_2"]);, then works as expected. Can I retrieve the dispatcherID in some other way? something like mcFly.dispatcher.ids["SecondStore"] ?


Edit: Oh... maybe it is related to: https://facebook.github.io/react/blog/2014/07/30/flux-actions-and-the-dispatcher.html#why-we-need-a-dispatcher

Problems arise, however, if we have circular dependencies. That is, if Store A needs to wait for Store B, and Store B needs to wait for Store A, we could wind up in an endless loop. The dispatcher now available in the Flux repo protects against this by throwing an informative error to alert the developer that this problem has occurred. The developer can then create a third store and resolve the circular dependency.

If that is... I'd be nice to see the warning.

Rethrown errors difficult to debug

I'm finding that a lot of times the reThrown errors from Action.js are quite ambiguous and hard to trace down.

Often the stack trace is not visible as it is rethrown from inside the Action.js and masks the origin of the actual error - combined with source maps this has even more inconsistencies.

Attempting to recreate an error seems to not be working right now but this is definitely happening - will post more details when it happens again.

Hidden errors

I've been using mcfly for a month now and I love it.
My project now is getting bigger and bigger and I found out a very annoying issue.

Many times errors in the onChange, or render Methods have any output in the console.
The only way to make then visible is to add try catch blocks inside each method.

Do you have any kind of clue why this is happening and how could I solve it?
I'm using react 0.12.2 and mcfly 0.0.8.

Thanks in advance!!

Textarea's cursor jumps when changing its value through McFly

So I came into the same exact issue as this one for reflux: reflux/refluxjs#43

Using Flux flow Action โ†’ Store โ†’ View
Like:

render: function() {
  return (
    <textarea
      value={this.props.value}
      onChange={this.handleChange}
    />
  );
},

handleChange: function(ev) {
  Actions.updateMessage(ev.target.value);
}

The problem is that typing anything when the cursor is not at the end of the box, will make the cursor to jump to the end of that box.

In the link I posted above, looks like that framework has a sync flag to make actions sync and the issue does not appear. I suppose here is happening because our actions are promises?

Any idea on how to solve this? Could we add to McFly a similar flag?

@tomatau did you run into similar issues in your projects?

McFly > 0.0.8 breaks in IE9 due to bind not available on console.{log,warn,error}

Amid the many strange things IE9 does, "its error/warn/log functions from the console object in IE don't inherit from Function. They are host objects, and use whatever rules IE sees fit." (quoting StackOverflow / Why can't I bind directly console.log on IE9 with developer tools open?

As a result, the var warn = (console.warn || console.log).bind(console) calls added as part of #31 and #26 and released in McFly 0.0.8 / 0.0.9 break it :-/ :

ie9-bind-not-on-consolelogwarn

I'd give a stab at fixing it, but am not sure how you prefer to solve this; if you don't have the time (or willpower to test on IE9 ^^), tell me what you'd like and I'll make a pull request.

Thanks for your work!

onChange renamed to something more descriptive?

What are your thoughts about renaming onChange to something more descriptive like "onStoreChanged"? Something I've noticed when taking a high level view of my components is that it's not clear exactly what "onChange" is for. Renaming it to "onStoreChanged" might help make that more clear.

Clearly that would be a breaking change, so if you wanted to avoid that (but we're pre 1.0 -- and pre 0.1 :), so why not!) you could add it as an alias.

  componentDidMount: function() {
    self.addChangeListener(this.onChange || this.onStoreChanged);
  },
  componentWillUnmount: function() {
    self.removeChangeListener(this.onChange || this.onStoreChanged);
  }

Hide use of dispatcherID with waitFor()

Since the assignment of store.dispatcherID is hidden from the caller of createStore(), maybe it would be worth modifying Facebook's dispatcher to do the lookup of dispatcherID itself. Then instead of:

mcFly.dispatcher.waitFor([myStore.dispatcherID])

we can do

mcFly.dispatcher.waitFor([myStore])

and the user never has to think about dispatcher ids.

Aren't actions tied to a specific Store?

It's sort of weird...
I have 2 stores and 2 different actions module.
Both actions contain

    switch(payload.actionType) {
      case 'SET':
        //do something

It now looks like when I trigger one action, the other also gets called.

`Uncaught TypeError: listener must be a function` is hard to trace

I removed an onChange from an example component. I edited a bunch of things, so I wasn't apparent that onChange was required and then the Uncaught TypeError: listener must be a function error was being thrown. No where in the stack trace pointed me to the file I had modified, so there was no way of knowing what mcfly was looking for.

Swap promises for ES6 generators and ES7 async await

I feel this would greatly improve the API usage and also improve error handling - making the code much clear to avoid things like the dirty reThrow function that lives inside Action.

This would require using the babel compiler (6to5) which also supports JSX if mcfly ever goes the route of involving any JSX.

e.g. Currently:

let MyActions = mcfly.createActions({
  actionCreator(foo){
    let scopedVar;
    return asyncCall.method(foo)
      .then(resolved=>{
        scopedVar = resolved.bar;
        return asyncCall.anotherMethod(resolved.derp);
      })
      .then(resolved2=>({
        actionType: Constants.EVENT,
        scopedVar, resolved2
      }))
  }

Would become, something like this (maybe?):

let MyActions = mcfly.createActions({
  actionCreator(foo){
    let resolved = await asyncCall.method(foo)
    let scopedVar = resolved.bar;
    let resolved2 = await asyncCall.anotherMethod(resolved.derp)
    return {
      actionType: Constants.EVENT,
      scopedVar, resolved2
    }
  }
}

Which reads much nicer imo and is forward thinking.

It would also help with #36

Store callback firing with undefined actionType

Hey everyone! I'm currently implementing a new feature at my company and experiencing a weird issue within my store where the callback fires first with an empty/undefined payload and then it fires again with the correct actionType and payload. Is there anything I should be looking for that could potentially be causing this? I looked over the examples and everything looks pretty straightforward and simple so far. Here is a short video of the behavior. Thanks!

video

Implementing optimistic updates with McFly

I was trying to work through how to implement optimistic updates with McFly (Fluxxor has a very good example of why we would need this). The main problem is that McFly actions dispatch a single payload, while optimistic updates require actions to dispatch multiple payloads (example: addTodo dispatching ADD_TODO, ADD_TODO_SUCCESS, and ADD_TODO_FAILURE).

One possible approach is to implement those extra payloads (ADD_TODO_SUCCESS, ADD_TODO_FAILURE) as separate McFly actions. Then, in the top-level action (addTodo) you can (a) immediately return the optimistic payload (ADD_TODO) and (b) fire off an AJAX request that dispatches the extra payloads.

What do you think? Should we add documentation to the project about this? I was thinking maybe a link in the README to a wiki page, or we can add it directly in the README.

Rename createAction

This could be slightly misleading for those coming from other Flux implementations. An action is generally the object that contains an actionType and is dispatched.

It might be more suitable to rename this to createActionCreatorsor just actionCreators

Action methods return promise object

Would be great if the methods on Action objects could return promise objects by default, it would allow behaviour such as:

var ComponentWithModal = React.createClass({
    _onSubmit(e){
        e.preventDefault();
        var inputNode = this.refs.inputNode.getDOMNode();
        Actions.addItem(inputNode.value)
            .then(function(){
                 this.closeModalWindow();
                 inputNode.value = "";
            }.bind(this))
            .catch(function(){
                 this.setError("Item could not be added");
            }.bind(this));
    },
//...

Perhaps returning false from an action would reject the promise. Currently would need to pass a callback function from the Component to the method which is kinda messy and inconsistent.

onChange fires after component is unmounted

I have several components using the same store, in this case it has application state, mainly a bunch of boolean's.

One of my components calls an action to toggle the value of one of the states, which then emits a change back to the component, but the component has by this time unmounted. What is the correct use case for handling this?

Thanks,
Dave

Feature Request: Actions map like Backbone events hash

I like the clean API wrapper you've got around the Flux architecture. The first thing that looked kinda smelly to me in the examples was the switch statements on action types. Seems like the sort of thing that could benefit from a friendly API wrapper like Backbones events hash.

I'm not super familiar with your code yet, but here is an idea of what I'm thinking would be exposed:

var TodoStore = mcFly.createStore({

  getTodos: function() {
    return _todos;
  }

}, {
    'ADD_TODO': function (payload) {
        // TODO: The needful, etc.
    }
});

Of course, you could keep the existing API and do a check for whether they are passing the actions hash or a function to handle everything themselves.

Possible to use without mixins?

I'm fairly new to React. Just wondered what the use of mixins provide and whether there's a way around them so I can use ES6 classes?

maxListeners

Hi,

I was having an issue where node was complaining about a potential memory leak because too many listeners were being added for the same event.

I saw that this was fixed in Biff (FormidableLabs@04edf8a) but not in this repo.

Is there a particular reason for that? Could we apply the same fix here?

Debug flag for the dispatcher

As it is written here:

http://www.code-experience.com/async-requests-with-react-js-and-flux-revisited/
There should be only one channel for all state changes: The Dispatcher. This makes debugging easy because it just requires a single console.log in the dispatcher to observe every single state change trigger.

I am missing this "console.log" when the Dispatcher is using "dispatch" so it's very easy and fast to see every time that some action has been dispatched (and I don't need console.log in all the Stores).

Will you be able to add some debug flag to set this console.log on or off? Something like:

Dispatcher.dispatch(payload)
if(DEBUG){
  console.log(payload)
}

Or maybe like React does:

if ("production" !== process.env.NODE_ENV){
  console.log(payload)
}

Thanks in advance!

Should verify both parameters are passed to `McFly.createStore`

Recently had a store that, at least initially, handled no actions. when creating it, I wrote this:

var ConfigStore = McFly.createStore({
    get: function(key){ 
        if(!_config[key]){
            console.warn("Config key '" + key + "' doesn't exist.");
        }

        return _config[key];
    }
});

Everything worked, so I assumed I was allowed to ignore the second param to createStore.

A little later, after I'd forgotten about this, I popped open the console in chrome and saw a fairly nasty stack trace coming out of the dispatcher. A couple hours of hunting later, I discovered my mistake.

It would be great if either:

  • When no callback is specified, an error is thrown stating that the callback is a mandatory parameter
  • When no callback is specified, a default, empty callback is substituted.

Will fork and submit a PR this weekend.


Stack:

TypeError: undefined is not a function
    at Dispatcher.$Dispatcher_invokeCallback 
    at Dispatcher.dispatch
    at
    at $$$internal$$initializePromise
    at new $$es6$promise$promise$$Promise
    at 
    at $$$internal$$tryCatch 
    at $$$internal$$invokeCallback 
    at 
    at MutationObserver.$$asap$$flush 

Impossible to do async tasks in an Action?

I feel it's important that async server operations be done in Actions -- not in Stores.

See here for further discussion: http://stackoverflow.com/questions/25630611/should-flux-stores-or-actions-or-both-touch-external-services

Problem is, McFly seems to force me to do synchronous action dispatch.

Any thoughts on how to get around this? Is this a flaw in the Action design in McFly, or do you think that McFly is opinionated in this way and prescribes async actions in the Stores?

Handling multiple dispatch action types

How should we let a single store to respond to actions of different types? E.G. you may want to differentiate between Server actions and Client actions for a separation of concerns but you would only want the single store responding to both.

You can see a flux implementation of this here https://github.com/facebook/flux/blob/master/examples/flux-chat/js/dispatcher/ChatAppDispatcher.js

Would it make sense to add instance methods to the McFly constructor that can add this behaviour?

Allow Multiple Independent Instances

I am trying to write an video player that has multiple with mcfly. I have everything working, but if I create multiple instances of the player on a single page, they seem to copy each other. I believe this comes from using a single Dispatcher for all new instances of mcfly. Is this desired behavior? Is there a way around it?

Problems testing with Jest and invariant

Hello Ken, I had some tests following this technique http://facebook.github.io/react/blog/2014/09/24/testing-flux-applications.html

However, I am having some troubles using McFly. I tried to write a test using your library example.

jest.dontMock('../CounterStore');
jest.dontMock('../../flux/mcFly');

describe('CounterStore', function() 
{
   it('expects store to be defined', function() {
     CounterStore = require('../CounterStore');
     expect(CounterStore).toBeDefined();
   });
});

This test fails.

What I do is add this to the package.json:

"jest": {
   "rootDir": ".",
    "scriptPreprocessor": "../jest-preprocessor.js",
    "unmockedModulePathPatterns": [
      "./node_modules/react",
      "./node_modules/mcfly"
    ]
 },

Then the test passes ok. However, in my project I also use react-tools. It's giving me a very weird error:

MyStore.js: __DEV__ ist not defined
.../node_modules/react-tools/src/vendor/core/invariant.js:26:7
.../node_modules/mcfly/lib/Store.js:21:5
.../node_modules/mcfly/lib/McFly.js:32:17

I saw in line 21 of mcfly Store there is:

invariant(!methods.callback, '"callback" is a reserved name and cannot be used as a method name.');

But it is using the invariant library from react-tools instead of the one from mcfly! Is this a library problem? Or am I using the packages very wrong? Not sure if this is mcfly related o a Jest problem.

This is not happening with the example of this library (still using 0.0.2) but if you change the package.json to:

 "dependencies": {
   "mcfly": "0.0.8",
   "react": "^0.12.0",
   "underscore": "^1.7.0"
 },

Then:

 - ReferenceError: /Users/xxx/Desktop/mcfly-master/example/js/stores/CounterStore.js: __DEV__ is not defined
    at invariant (/Users/xxx/Desktop/mcfly-master/example/node_modules/react-tools/src/vendor/core/invariant.js:26:7)
    at new Store (/Users/xxx/Desktop/mcfly-master/example/node_modules/mcfly/lib/Store.js:21:5)
    at McFly.createStore (/Users/xxx/Desktop/mcfly-master/example/node_modules/mcfly/lib/McFly.js:32:17)
    at /Users/xxx/Desktop/mcfly-master/example/js/stores/CounterStore.js:9:26

EDIT:
If in Jest configuration I do:

"globals": {"__DEV__": true}

Then all tests are ok but I feel like cheating? But actually if I try:

var mockStore.mcFly.createStore({
  mixin: function(){
    return null
  }
    ...
})

Then it tells me:

Invariant violation: "mixin" is a reserver name and cannot be used as a method name

(which is from McFly invariant so... maybe I am not doing anything wrong at the end).

Example using RequireJS

For some reason I cannot manage to use mcfly in my project with RequireJS.

It looks like mcfly cannot find its Dispatcher.

I must be missing something.

Any help would be useful.

Cannot npm install with Git URL

Seems to be because lib directory is not present on GitHub, but the package.json refers to it.

I'd like to try out the master branch, but I cannot ๐Ÿ˜ญ

Biff or McFly

Is Biff the new home for McFly?
General activity, would lead me to believe that it is now?
Thanks.

Store emit different change names

Right now Store only have emitChange() function to emit change to react component, which also has only one onChange listener to listen to the change event.

So I want store can emit different change names, and the components can have multi listeners.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.