Code Monkey home page Code Monkey logo

refluxjs's Introduction

RefluxJS

A simple library for unidirectional dataflow architecture inspired by ReactJS Flux.

NPM Version Bower Version Build Status NPM Downloads

Sauce Test Status


Installation

You can currently install the package as a npm package, a bower component, or import it from a CDN.

NPM

The following command installs RefluxJS as a npm package:

npm install reflux

Then, in your script, you can gain a reference to RefluxJS like so: var Reflux = require('reflux');

Bower

The following command installs reflux as a bower component that can be used in the browser:

bower install reflux

Then the files may be imported into your html file via bower_components/reflux/dist/reflux.js or bower_components/reflux/dist/reflux.min.js. At that point a Reflux variable will be globally available to you. It is suggested that you import RefluxJS after React.

CDN

RefluxJS is available at jsdelivr.

You may import the CDN files directly through a script tag. At that point a Reflux variable will be globally available to you. It is suggested that you import RefluxJS after React.


Overview

The main function of Reflux is to introduce a more functional programming style architecture by eschewing MVC like pattern and adopting a single data flow pattern.

+---------+       +--------+       +-----------------+
¦ Actions ¦------>¦ Stores ¦------>¦ View Components ¦
+---------+       +--------+       +-----------------+
     ^                                      ¦
     +--------------------------------------+

The pattern is composed of actions and data stores, where actions initiate new data to pass through data stores before coming back to the view components again. If a view component has an event that needs to make a change in the application's data stores, they need to do so by signaling to the stores through the actions available.


Usage

For usage, you need to create actions which can be called from React components. Those actions are listened to by stores which hold and update data. In turn those stores are hooked up to React components and set state within them as it is updated within the store.

Therefore the 3 main concepts to know are:

  1. creating actions
  2. creating stores
  3. hooking stores to React components

Creating Actions

Create an action by calling Reflux.createAction with an optional options object.

var statusUpdate = Reflux.createAction();

An action is a function object that can be invoked like any other function.

statusUpdate(data); // Invokes the action statusUpdate

There is also a convenience function for creating multiple actions.

var Actions = Reflux.createActions([
    "statusUpdate",
    "statusEdited",
    "statusAdded"
]);

// Actions object now contains the actions
// with the names given in the array above
// that may be invoked as usual

Actions.statusUpdate();

More on Actions:

Actions can also:

  • load files asynchronously with child actions
  • do preEmit and shouldEmit checking
  • have many shortcuts for easy usage

See Reflux Action Documentation for more.


Creating Stores

Create a data store much like ReactJS's own React.Component by creating a class extending Reflux.Store. The store has a state property much like a component, and uses setState like a component as well. You may set up all action listeners in the constructor and register them by calling the store's own listenTo function.

class StatusStore extends Reflux.Store
{
    constructor()
    {
        super();
        this.state = {flag:'OFFLINE'}; // <- set store's default state much like in React
        this.listenTo(statusUpdate, this.onStatusUpdate); // listen to the statusUpdate action
    }

    onStatusUpdate(status)
    {
        var newFlag = status ? 'ONLINE' : 'OFFLINE';
        this.setState({flag:newFlag});
    }
}

In the above example, whenever the action statusUpdate is called, the store's onStatusUpdate callback will be called with whatever parameters were sent in the action. E.g. if the action is called as statusUpdate(true) then the status argument in the onStatusUpdate function is true.

Stores also integrate easily with sets of actions via things like this.listenables. When an actions object (or an Array of multiple actions objects) is applied to this.listenables you may automatically add listeners simply by naming convention. Just name the functions either after the action name (such as actionName, or the camelcased action name preceded with "on", (such as onActionName).

var Actions = Reflux.createActions(['firstAction', 'secondAction']);

class StatusStore extends Reflux.Store
{
    constructor()
    {
        super();
        this.listenables = Actions;
    }

    onFirstAction()
    {
        // calls on Actions.firstAction();
    }

	onSecondAction()
	{
		// calls on Actions.secondAction();
	}
}

More on Stores:

Reflux stores are very powerful. They can even do things like contribute to a global state that can be read and set for partial or full-state time-travel, debugging, etc.

See Reflux Store Documentation to learn more about stores.


Hooking Stores to Components

Once you've created actions and stores, now the last step in working RefluxJS is to hook those stores to a React component.

This is done as simply as extending Reflux.Component instead of React.Component and setting the store(s) to use. Reflux.Component itself extends React.Component, so you use them the exact same way. The only difference is that Reflux.Component allows you to set stores for the component to get state from:

class MyComponent extends Reflux.Component
{
    constructor(props)
    {
        super(props);
        this.state = {}; // our store will add its own state to the component's
        this.store = StatusStore; // <- just assign the store class itself
    }

    render()
    {
        var flag = this.state.flag; // <- flag is mixed in from the StatusStore
        return <div>User is {flag}</div>
    }
}

When the component mounts it will either create a singleton instance of StatusStore (if one isn't already made) or use an already made singleton (if it was already created by another component that uses the store).

Of important note is that you can:

  1. Set multiple stores by setting this.stores (the plural) and setting it to an Array of store classes.
  2. Set a this.storeKeys Array to restrict only certain parts of the store being mixed into the component state.

There is also a mapStoreToState method in the documentation for those that want absolute control over how a store's state is mapped to a component.

class MyComponent extends Reflux.Component
{
    constructor(props)
    {
        super(props);
        this.state = {type:'admin'}; // <- note that we can still use normal state
        this.stores = [StatusStore, AnotherStore];
        this.storeKeys = ['flag', 'info'];
    }

    render()
    {
        var flag = this.state.flag;
        var info = this.state.info;
        var type = this.state.type;
        return <div>User is {flag}, info: {info}, type: {type}</div>
    }
}

The above will mix in properties from the state of both StatusStore and AnotherStore. However, because of this.storeKeys it will only mix in the properties flag and info from them. So any other properties within those stores will not get mixed in. So even if a store contained a type property in its state it would not get mixed in, and the type value we set as a normal part of the component state is safe.

More on using Reflux style components:

Reflux's simple and intuitive way of integrating stores into components is easy and powerful. You can aggregate stores together on a component-by-component basis, filter which parts of the stores come through and which don't, or even do a detailed manual mapping of exactly how you want the state from stores to map to the state in a particular component.

See Reflux Style Component Documentation to learn more.


Documentation

What you've just read is a "view from 10,000 feet" type overview of getting started with RefluxJS. For serious learning see the documentation.

refluxjs's People

Contributors

bripkens avatar bryangrezeszak avatar ccapndave avatar dan-weaver avatar dashed avatar devinivy avatar deviousdodo avatar dtinth avatar ericclemmons avatar heldr avatar iofjuupasli avatar ivan-kleshnin avatar krawaller avatar kyleamathews avatar maxguzenski avatar mikhuang avatar patrick-webs avatar richardlitt avatar robcolburn avatar rymohr avatar shraymonks avatar simondegraeve avatar snorp avatar spoike avatar srph avatar turbo87 avatar undozen avatar vcarl avatar vslinko avatar willembult 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  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

refluxjs's Issues

Error is thrown depending on order when listeners are registered

The following error is produced in certain configurations on this line:

Uncaught TypeError: Cannot read property 'hasListener' of undefined

This happens when you register composed listeners before other stores and actions. The args array has a length set, but is an array of undefined.

Testcase pseudo code:

// Inside a store init
var join = Reflux.all(testAction1, testAction2);

this.listenTo(join, resetCallback);
this.listenTo(testAction2, commonlyCalledCallback); // throws the error from here

Workaround is to listen to the joined actions/stores afterwards:

this.listenTo(testAction2, commonlyCalledCallback);
this.listenTo(join, resetCallback);
// no errors thrown, works as expected

Revise API?

After looking at various flux implementations, I share some of @spoike's sentiments on certain design decisions that facebook adopts in their flux implementation (which every other flux implementation seems to copy).

I really like the idea that actions/stores are like functors. But I feel that its implementation and constructor via Reflux.createAction and Reflux.createActions doesn't seem right, especially for the fact that every action is basically an EventEmitter; which I think is overkill. I try to reconcile this by having Reflux.createActions share the EventEmitter instance among related actions.

Thus, I'm suggesting a revised API.

Rather than having each action become an EventEmitter, I think it makes more sense if they are instead "channels" of an EventEmitter. You can still create functors by wrapping around the associated emit function, which in this case becomes a one-to-many pub/sub topology.


I'd like some thoughts on the suggested revised API. The stores API is inspired by orchestrator used in gulp.js.

// revised Reflux API
var Reflux = require('Reflux');

/**
 * Actions
 */

var todoActions = new Reflux.Actions();

// shorthand
todoActions = new Reflux.Actions(['create', 'delete', 'edit']);

// shorthand for single action
todoActions = new Reflux.Actions('create');

// string array of unique action names
todoActions.add(['create', 'delete', 'edit']);
todoActions.add('create'); // shorthand

// alias for above
todoActions.register(['create', 'delete', 'edit']);

// should be rare to do this
todoActions.remove(['create', 'delete', 'edit']);
todoActions.remove('create'); // shorthand

todoActions.execute('create', args);
// alias for above
todoActions.emit('create', args);

// reference as a function (functor representation)
create = todoActions.get('create');

// alias for todoActions.execute('create', args);
create(args);

// with no args, get object with accessible functors as properties
actions = todoActions.get();
actions.create(args);

// shorthand for todoActions.get();, but doesn't accept any args
todoActions.list();
// output: ['create', 'delete', 'edit']


unlisten = todoActions.listen('create', handler);

// listen to functor
unlisten = create.listen(handler);

// hook functions.
todoActions.shouldEmit('create', handler);
todoActions.preEmit('create', handler);

// whenever a handler listens/unlistens to an action
todoActions.onListenerAdd('create', handler);
todoActions.onListenerRemove('create', handler);


// hook functions on functors
create.shouldEmit('create', handler);
create.preEmit('create', handler);

// whenever a handler listens/unlistens to an action
create.onListenerAdd(handler);
create.onListenerRemove(handler);

// misc
todoActions.removeAllListeners();
create.removeAllListeners();

// alias for todoActions.remove('create');
create.destroy();


/**
 * Stores
 */

var todoStore = new Reflux.Store();

/**
 * trigger - action or store for handler listen to
 * [dependencies] - array of action(s)/store(s) to wait on
 * handler - callback
 *
 * handler gets executed whenever trigger executes. However if dependencies are defined,
 * then whenver trigger executes, dependencies must be executed in their order before
 * handler is finally executed.
 *
 * returns destroy function that removes handler from trigger.
 *
 * TODO: should use promises with timeouts? (bluebird)
 * TODO: use such timeouts only in dev mode? (webpack, browserify, et al)
 */
destroy = todoStore.on(trigger, [dependencies], handler);

// [someAction, someStore] may be promises.
// destroy removes
todoStore.on(action, [someAction, someStore], function() {

    this.done('some result');

});

unlisten = todoStore.listen(handler);

// whenever a handler listens/unlistens to the store
todoStore.onListenerAdd(handler);
todoStore.onListenerRemove(handler);

// misc
todoStore.removeAllListeners();

Wrap action functions in setTimeout?

It's a common use case that action functors are normally tied to virtual DOM event callbacks within components in react.js. EventEmitters (node.js or 3rd-party) are not async in nature, unless otherwise designed to be.

Thus, whenever an emit function is called, it's done synchronously which begin from the action to the store (and chained stores).

Action functors should be executed in fire-and-forget fashion. Thus, I think we should wrap action functions in setTimeout with 0ms delay.

@spoike thoughts?

Circular checks are broken

Listening to stores in components are broken in 0.1.8.

More info will come as I am debugging this now.

Create multiple actions convenience function

Add to the Reflux API a way to easily create multiple actions to an object with an array of names as parameter. Example:

var Action = createActions([
    "activateGem",
    "deactivateGem",
    "openPopup",
    "closePopup"
  ]);

console.log(Action);
// Should output something like this:
// {
//     activateGem: {Action},
//     deactivateGem: {Action},
//     openPopup: {Action},
//     closePopup: {Action},
// }

Reflux.all() also needs Reflux.waitFor()?

First, thanks Reflux, I'm enjoying using it!

I have an aggregate store that depends on two others:

Store1 Store2
\ /
AggregateStore

Right now I have AggregateStore listening to the other stores using listenTo. This works, but the aggregate computation can be quite expensive. So if I have some action that touches both Store1 and Store2 the result is that the aggregate computation runs twice. This gets expensive, and makes debugging a bit trickier since the flow passes through the aggregate function multiple times for a single action.

I looked at using Reflux.all to solve the problem. It solves the multiple aggregate computations nicely but introduces a new problem.

Some actions only affect Store1, and in that case I'd still like my AggregateStore to still update immediately with respect to the original action: using the new value from Store1 and the existing value from Store2. But Reflux.all will only fire after both Store1 and Store2 have fired. So in some cases my AggregateStore isn't updated for a long time.

I think Reflux.all() is nice, but I think it also needs a Reflux.waitFor() counterpart that works more like Facebook Flux's waitFor(). Is something like this in the works, or are there suggested workarounds?

Composed listenables fails in Opera

According to testling, the following test fails in Opera and older versions of Chrome:

not ok 8 Composed listenables should emit multiple times
  Error: Uncaught AssertionError: expected [] to deeply equal [ Array(2) ] (http://git.testling.com/work/spoike/repos/ba944a00a042aeeb2ec80d119d93684beb9ff196.1407304513039/1407304513279.c3212e4f.js:2319)
      at global.onerror (http://git.testling.com/work/spoike/repos/ba944a00a042aeeb2ec80d119d93684beb9ff196.1407304513039/node_modules/mocha/mocha.js:5871:10)

Reflux.createActions needs more work?

PR at #13 is a baby step; which shares EventEmitter among group of actions.

This allows the possibility of certain features:

1. Should we bother adding/removing actions to/from context/namespace/group of actions?

May need to use Object.defineProperty for custom getter/setters to register/unregister to the namespace's EventEmitter. Since react.js supports IE8, Object.defineProperty will need to be polyfilled.

  1. Assign hook functions when calling actions. Hook functions get executed before/after each emit.

    Inspired by mount lifecycle of react.js.

    Proposed example:

    var Actions = Reflux.createActions([
      "statusUpdate", 
      "statusEdited",
      "statusAdded"
    ]);
    
    // assign hook functions
    
    // executed prior to emit
    Actions.statusUpdate.preEmit = function() {};
    
    // test whether emit should occur. Executes after preEmit.
    Actions.statusUpdate.shouldEmit = function() {};
    
    // Alternative syntactic sugar -- only assign preEmit functions
    
    var hooks = {};
    hooks.statusUpdate = funnction() {};
    hooks.statusEdited = funnction() {};
    
    // Map based on keys from hooks.
    var Actions = Reflux.createActions(hooks);
    
    // add/remove/change hook functions
    Actions.statusUpdate.shouldEmit = funnction() {};

Throw error when stores are chained in a circular loop

Story

As a developer using reflux
When I accidentally chain stores in a circular loop
Reflux.listenTo should throw an error notifying of the circular dependecy

Do some listener dependency tracking on actions and stores to make sure that developers don't accidentally do circular dependency and have an indication where it went wrong.

bind methods in store

Methods in React components are automatically bound to the component instance, saving you from having to do any this.onClick = _.bind(this.onClick,this) stuff.

This is convenient and something you quickly get used to, so it feels like an annoyance not to have this convenience in Reflux stores. This crops up pretty often, as stores frequently need to use methods as callbacks for various databases, REST API:s etc.

So, question; should store methods be autobound to the instance?

Reflux and FSM correlation

Just diving into React and of all the places I've seen this is likely the best place to ask my question. The mechanism I thought would work well for my app is a FSM, where the app functionality is contained in machine states that are transitioned between via UI events. Each state would have its own exchangeable model data that configures how it edits the main content model of the app.

My app is interaction oriented so I see FSM states/handlers as being the main drivers (instead of views), which in turn each decorate the UI with their own view additions. There is a main UI layer/component that the main FSM is attached to and transitioned from, then each unique FSM state may render its own further UI components and edit the main content model with its own actions.

What I'm reading about Flux and the improved Reflux sounds somewhat suitable for implementing a FSM? Do FSM states each related to separate stores and sets of actions? It would be great to learn how/if Reflux might be suited for a FSM control structure.

Potential arguments leakage?

Saw this in another issue: pixijs/pixijs#853

Other sources on this:


May be relevant in reflux where arguments is potentially leaked. AFAIK, this is something for V8.

Points of interest:

  1. https://github.com/spoike/refluxjs/blob/ff67e1d996f44cf87e38d45a855f3b4291572d01/src/createStore.js
    Leak at line 44, which is caught in line 34. We shouldn't trust what happens in EventEmitter.emit.
  2. https://github.com/spoike/refluxjs/blob/ff67e1d996f44cf87e38d45a855f3b4291572d01/src/all.js#L58
    line 58: classic pattern. Use patch mentioned here: tj/node-thunkify#12

/cc @spoike @bripkens

Create convenience mixin to manage subscriptions

Each component needs to unsubscribe to avoid havoc. Having a component subscribe to multiple listenables only complicates the situation.

I propose to add a mixin which manages the subscriptions and takes care of unsubscribing. It may look similar to the following:

var ListenerMixin = {
  componentWillMount: function() {
    this.subscriptions = [];
  },

  listenTo: function(listenable, callback) {
    var unsubscribe = listenable.listen(callback, this);
    this.subscriptions.push(unsubscribe);
  },

  componentWillUnmount: function() {
    this.subscriptions.forEach(function(unsubscribe) {
      unsubscribe();
    });
  }
}

reflux and angular

Hey, i just wanted to start by saying that this library is really great. It's finally allowed me to really wrap my head around functional reactive programming and implement it easily. nice job.

I wanted to put some thoughts in here about implementing this in Angular and see what people might think about that. I'm using the angular-fullstack repo from DaftMonk as a starting point, and i have to say it's actually really great. It has allowed me to leverage the overall workflow of flux, the pub/sub aspect of of the stores and actions, sockets, and the 2way data-binding of directives.

So, you can create a Reflux factory, allowing you to trace all your reflux dependancies:

angular.module('refluxApp')
  .factory('RefluxService', function () {
    return Reflux;
  });

Then wrap your actions in angular factories as well:

.factory('ThingActions', function(RefluxService){
  return RefluxService.createActions([
      "thingRemove",
      "thingAdd",
      "thingUpdated",
      "thingLoad",
      "thingSocketSync",
      "thingSocketUnsync",
      ]);
})

And do the same for your Stores. You'll notice that i'm actually plugging this store into the socket.io service, and auto triggering events based on that. it works out quite well. I'm also auto loading this module after it's injected by using this.onLoad() in the init function. Sorry that this example is probably a little poorly constructed. it was just a proof of concept:

.factory('RefluxThingStoreService', function($http, socket, RefluxService, ThingActions){
    // Creates a DataStore
    return RefluxService.createStore({

        // Initial setup
        init: function() {

            // Register action
            this.listenTo(ThingActions.thingAdd, this.onAdd);
            this.listenTo(ThingActions.thingLoad, this.onLoad);
            this.listenTo(ThingActions.thingLoading, this.onLoading);
            this.listenTo(ThingActions.thingLoaded, this.onLoaded);
            this.listenTo(ThingActions.thingSocketSync, this.onSocketSync);

            this.things = [];

            this.loaded = false;

            this.onLoad();
          },

        onLoad:function() {
            if(this.loaded === false) {
                ThingActions.thingLoading();
            } else {
                console.log('already loaded')
            }
        },
        onLoading:function() {
            var ths = this;
            this.trigger('loading');
            api.get().success(function(awesomeThings){
                    _.each(awesomeThings, function(thing){
                        ths.things.push(thing);
                    });
                    ThingActions.thingSocketSync();
                    ThingActions.thingLoaded();

            });
        },
        onLoaded:function() {
            this.loaded = true;
            this.trigger('loaded');         
        },
        onSocketSync:function() {
            socket.syncUpdates('thing', this.things, function(event, item, array) {
                this.trigger(event, item)
            }.bind(this));
        },
        onSocketUnsync:function(){
            socket.unsyncUpdates('thing');
        }
    });

  })

So now i have three angular factories, one for Reflux, one for actions, and one for the datastore. What i believe to be great about this is that I can leverage Angular's dependency injection to track what directives, controllers, or services are using these.

It also becomes very easy to build state into my directives. One of the challenges i am trying to tackle is how to make UI components respond to the state/status of data as it flows through the system without modifying the actual data object. the streaming events situation seems to solve this.

here's an example of a directive that adds a progress box whenever a new thing is added.

  .directive('createThingProgress', function (RefluxService, ThingActions, RefluxThingStoreService) {
    return {
      restrict: 'EA',
      link: function (scope, element, attrs) {

        var Progress = ThingActions.thingAdd.listen(function(thing){
            var progressBar = angular.element('<div class="well progressBar"><h3>building '+thing.name+'</div>');
            element.append(progressBar);
            scope.$digest();
        });     

        var Completed = RefluxThingStoreService.listen(function(status, payload){
            if(status==='created') {
                var thing = element.children('.progressBar');
                thing.remove();
            }
            scope.$digest();
        });     

        scope.$on('$destroy', function(){
            Progress();
            Completed();
        });
      }
    };
  });

in this example, i'm using the listeners to stores and actions to modify the UI elements. but you can also leverage Angular's dirty checking to update data placed on the scope from a store.

Anyway, implementing things this way has been very successful for me on this initial test. I'm excited about exploring this framework more and interested to see what you guys think about this approach.

Tracking strategies for Reflux.all

As detailed in #28 by @bripkens. The following strategies exist:

  • trackLastCall (currently implemented in #28 for Reflux.all) - takes the last argument received for each
  • trackFirstCall - takes the first argument received for each
  • trackAllCalls - mashes all arguments together
  • ensureEqualNumberOfCalls - throws error if something was called more than once before the promise was completed

Add hooks for actions

As mentioned in #14, add hooks for actions: preEmit and shouldEmit. Reminiscent of life cycle functions in React.

Extend the listener mixin to provide "initial" data

Add the possibility to fetch initial data from a data store to a React component. This needs a change to the ListenerMixin's listenTo method and in the store implementation as well.

Motivating Example

var aStore = Reflux.createStore({
    init: function() {
        this.listenTo(anAction, function() {
            this.trigger('not initial data anymore');
        });
    }
    getInitialData: function() {
        return "the initial data";
         // is used by Listenermixin if a component wants initial data 
    }
});

var Component = React.createClass({
    mixins: [Reflux.ListenerMixin],
    getInitialState: function() {
        return {}; // <-- impossible to know the state if stores aren't initialized yet
    },
    componentDidMount: function() {
        this.listenTo(aStore, changeCallback, changeCallback);
            // will call changeCallback twice, during store change and 
            // when the store has initialized
            // third callback should be optional
    },
    changeCallback: function(data) {
        console.log(data);
        this.setState({
            data: data
        });
    }
});

In the example, before invoking the anAction the component should be able to set the state with "the initial data" fetched from the store's getInitialData method. When invoking the anAction the component should be able to set the state with "not initial data anymore" fetched from the change event.

Thoughts?

Create/fork TODO example app?

This project really interests me and I'm integrating it into my app.

It would be great, however, to demonstrate the "reflux way" using the the TODO app as an example. (perhaps just forking https://github.com/facebook/flux/blob/master/examples/flux-todomvc)

For example, I'm particularly interested in the best way to set initial state for components. In the Facebook TODO app example, they defined a function that gets called when the store emits its change event:

function getTodoState() {
  return {
    allTodos: TodoStore.getAll(),
    areAllComplete: TodoStore.areAllComplete()
  };
}

In the Reflux examples I've seen, state is passed passed directly to the callback rather than the component calling store methods. Of course the same thing could be done with Reflux but I'm not sure if that would be missing the point.

In short, it would be useful to see how the author of the project uses the library in a more detailed way with React.

Feature Request: Example with Server

Hey there,

Just came across this - it looks really cool, and I find the concepts behind this really intuitive, compared to some of the other Flux implementations out there. One thing that I'd find useful, and might be good to include in the docs, is how to integrate Reflux with fetching data from a server (or really, any other sort of asynchronous code).

Thanks!

Component reusability

Hi, how would one reuse a component using the (Re)flux architecture?

For instance the ToDo example, what if I would like to have multiple instances of ToDo lists on a page( Todo today, todo tomorrow etc). Because of the singleton nature of the Actions(dispatchers) all Todos would be updated on all actions triggered.

Cache actions and stores for easy introspection

Story

As a user
I want to find all created actions AND stores from Reflux module
So that I can: trigger actions AND/OR tap on what they emit in the browser console

Explanation

At the moment it is up to the web application to hold all the created actions and stores. This proposal is for creating a action and store cache that is exposed from e.g. Reflux._actions and Reflux._stores.

This makes it easier to do introspection from a browser console, and opens up the possibility to create browser extensions that can:

  • tap into what data the actions and stores do emit
  • create a map of actions and stores
  • easily trigger an action for development testing

Add .jshintrc

For better code standards and less merge conflicts!

As mentioned in #13

preEmit and shouldEmit in actions

Do we really need both preEmit and shouldEmit? Code from preEmit could just as easily be put inside shouldEmit instead.

I see the values of the explanatory names and not having to actively return true if you just want to run code pre-emission, but is that worth muddying the API?

As for having to return true to proceed, we could switch it so that emission is cancelled if return value is true.

Add support for eventstream-like objects

Add support for listening to eventstream like objects. E.g. eventstreams from bacon.js or event-stream.

Under the hood we only need to check if the listenable object has a map function (which means also arrays may be listened to as well).

Motivating example

You will now be able to simply create eventstreams to be used as "automatic" actions or stores. E.g. resize event in bacon.js:

var resizeStream = $(window).asEventStream('resize');

var ResizeComponent = React.createClass({
    mixins: [Reflux.ListenerMixin],
    componentDidMount: function() {
        this.listenTo(resizeStream, this.onAppResize);
    },
    onAppResize: function() {
       // is called everytime the window resizes
    },
    render: function() { /* ... */ }
});

Thoughts?

Remove lodash dependency

First of: I really like where you are going with reflux and you mentioned many of my pain points with Flux, thanks!

It seems like the lodash dependency could be removed by implementing a custom assign and isFunction functions. Removing it seems desirable to reduce reflux's impact on page load time.

pubsub vocabulary

Salvaging the discussion from the closed PR; currently we talk about listeners and publishers. This is a slightly unfortunate mix of vocabs, listener/listenable and subscriber/publisher.

Should we not care, or maybe move to publisher subscriber all across? As @spoike said, listener/listenable is hard to distinguish and doesn't really roll off of the tongue.

Add callback for map/reduce capability on actions

From discussion in #57 it seems we may need to create actions together with a callback that works like the preEmit.

Add function as argument in createAction so you can do this:

var myAction = Reflux.createAction(function(arg) {
    console.log(arg);
    return arg + 1;
});

myAction.listen(function(arg) { console.log(arg); })

myAction("test");
// will output:
// test
// test1

Internally we could just pass the callback to the preEmit hook. Also the action needs to pass the return value preEmit does. By default, if preEmit does not return anything (i.e. returns undefined), the arguments are kept as is.

Creating multiple actions is done through an options object:

var Actions = Reflux.createActions({
    action1: true, // or any truthy value
    action2: function(arg) {
        console.log(arg);
        return arg + 1;
    }
});

Actions.action1(); // no preEmit hook, just a default action
Actions.action2("test"); // outputs test, passes on "test1"

Recommended way of initializing Store with data before first render?

I'm currently doing

myStore.update(data);
React.renderComponent(App, …)

where update is my custom method for updating store's internal list of objects and calling trigger.

I have to update store directly instead of using an action because actions fire asynchronously, and I wouldn't be able to use renderComponent's callback to check when app has finished rendering with data.

In App:

function getAppState() {
  return {
    myState: myStore.getState()
  };
}

...

getInitialState: getAppState,

_onChange: function() {
  this.setState(getAppState());
},

componentDidMount: function() {
  this.listenTo(myStore, this._onChange);
},

Any better way to go about this? Cheers.

Handle events efficiently

Getting this warning message from EventEmitter when creating a lot of stores and actions.

(node) warning: possible EventEmitter memory leak detected. 11 listeners added. Use emitter.setMaxListeners() to increase limit.

Need to rewrite event handling a bit, since for larger web applications there will be a lot of event emitters around so this warning is not very interesting to have.

Still need waitFor?

I'm really liking in general the simplifications you've made over Facebook's Flux implementation. But one possible weakness with your approach of listening to stores vs. waiting for stores is stores lower in the hierarchy would lose access to the raw action data. This would be a problem if the top-level store only preserves a filtered view of the raw action data. A lower-level store could get around this to some degree by also listening to an action and storing its data and then later combining that with the higher-level store's data but then there could be problem ensuring matching up data.

So in short, I see value still in having a way of doing ordered event callbacks.

Throttled actions

Add the possibility to create actions that are throttled, i.e. they only fire at regular intervals, for actions where it is known that it will get spammed from user events (most likely from mouse events) but where listeners don't need to listen to every invocation and is only interested in a reduced set of invocations.

The suggestion is that users that invoke the action can choose to use a throttled version instead:

// Create actions as usual:
var myAction = Reflux.createAction();
myAction.listen(function(num) {
  console.log(num);
});

// Get a throttled variation of `myAction`:
var throttledAction = myAction.throttled(50);

// Test:
var i = 0;
for(;i <= 10; i++) {
  throttledAction(i);
}
// Should output: 
// 0
// 10

Store ---> Hub?

Here's a curve ball; isn't "store" really a misnomer? It's not necessarily a store for anything. But if you do want to store something, it is a convenient place to deal with that.

What is it then? An event emission aggregator. I'm thinking node, or hub.

So, perhaps createHub? What do you think?

Textarea's cursor jumps when changing its value through reflux

Hello,

I'm experiencing an issue where if I update a textarea's value through the Action → Store → View flow, with the cursor not at the end, the cursor will jump to the end. The same doesn't happen if I use state internally in the component to keep track of the current value, like in this example.

Does this by an off chance have to do something with this?

Action and store callbacks are not executed in a single synchronous iteration

The code is something like this:

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

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

Also noticed this one, facebook/react#1698

Listen methods on actions have no context set

The listen method on actions have lost their this context after 0.1.8. Example code:

// Using a bacon stream to listen to an action:
var actionStream = Bacon.fromBinder(myAction.listen);

// will crash on 0.1.8

Workaround for now is to bind the action as context, example with lodash:

var actionStream = Bacon.fromBinder(_.bind(myAction.listen, myAction));

Using reflux with browserify

I don't know if this is something wrong with my project configurations or with reflux, but when I try to use browserify, I get the following error:

Error: Cannot find module 'browserify-shim' from 'PROJECT_PATH/node_modules/reflux'

Any suggestions?

Joining parallel listeners to one

I'd like to propose an alternative to the waitFor function to something that makes more sense in a data flow pattern. As described with #18, there are occasions where you're interested in data coming in parallel. The API should be something as simple as a join function (name somewhat inspired by the parallel workflow concept):

var dataStore = Reflux.createStore({
    init: function() {
        this.listenTo(anAction, this.callback)
            .join(anotherAction, anotherStore /*, ...*/);
    },
    callback: function(anActionArgs, 
        anotherActionArgs, 
        anotherStoreArgs /* , ... */) {
            // ...
    }
});

It would be awesome if we didn't need to pull in a promises library dependency for this.

Please do let me know if we can improve this API before implementing it.

Make proper mixin interfaces for Store and Action interfaces

Refactor createAction and createStore factory methods to use object literals defining an Action and an Store respectively to be used as mixins when creating the action functors and store objects. We're sort of doing this in createStore but we're creating new Store functions and extending them, I feel we can move the Store prototype outside to an object literal and have that mixed into the final store object's prototype.

This comes with two benefits:

  • Makes it possible for others to extend the Action and Store mixins themselves (probably expose this through Reflux.mixins.Action and Reflux.mixins.Store properties or something)
  • Making documentation more straightforward once we need to do API docs for the project homepage. This refs #2.

Asynchronous dataflow example

It would be great with an example on how to deal with asynchronous data sources along with optimistic updates in Reflux. Remote data is something everyone works with and something that Flux documents very poorly.

I suppose one way would be creating several events for each action (Todo.createItem, Todo.createItemSuccess and Todo.createItemFailure) and using the first to do optimistic updates.

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.