Redux-like extension for Nanoflux
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
dist
perf
spec
src
.gitignore
.npmignore
.travis.yml
LICENSE
README.md
gulpfile.js
index.js
package.json

README.md

Build Status npm version GetBadges Game

nanoflux-fusion

PROJECT SITE

Note: Fusion is still in beta, being tested on some real world examples. You may use it already. If you encounter issues feel free to report them!

A Redux-like extension for Nanoflux.

nanoflux-fusion is built on top of nanoflux, a quite efficient Flux implementation,
and adopts the concept of reducer functions for application state management, making Flux even more comfortable.

Install

Easy peasy lemon squeezy!

npm install nanoflux-fusion --save

Concept

Dan Abramov pushed the Flux evolution with Redux and proved that application state management is possible with a very minimalistic approach. The idea of using reducer functions to alter the state is very elegant. nanoflux-fusion adopts the idea of reducer functions (called Fusionators, because naming is fun) to make Flux even more comfortable. That way, it's not necessary to define any Store. The user just focus on how he wants to alter the state using functions the simply return the new state.

Example

	var NanoFlux = require('nanoflux-fusion');
	
	// NanoFlux provides a dedicated store
	var fusionStore = NanoFlux.getFusionStore();
	
	// subscription is the same as in NanoFlux, note that the function passes a state (which is immutable)
	var subscription = fusionStore.subscribe(this, function(state){
		// ... do something with the state
		// state is also available via fusionStore.getState()
		console.log("Items:", state.items);
	});
	
	// the 'fusionator' is responsible for the state manipulation
	// it is called with two arguments, the previous state
	// and an arguments array containing the arguments passed on actor call.
	NanoFlux.createFusionator({
		
		addItem : function(previousState, args){
			// the previous state is frozen/immutable, so we need to copy the items to update the list
			var currentItems = previousState.items.slice(); 
			currentItems.push(args[0]);
			// we don't need to return the entire state. 
			// A partial state will be merged
			// As builtin feature, the new state is being frozen (deeply)
			return { items : currentItems };
		},
		removeItem : function(previousState, args){
			if (previousState.items.length == 0) return {};

			// note: filter creates a copy already 
			var items = previousState.items.filter(function (item) {
				return item.name !== args[0];
			});
			return {items: items}
		}
	}, 
	// define an initial state!
	{
	 items: []	
	});
	
	// gets the fusion actors, i.e. have the same name as defined above
	var addItem = NanoFlux.getFusionActor("addItem");
	var removeItem = NanoFlux.getFusionActor("removeItem");
	
	// use the actors as simple action functions
	addItem({ name: "item1", value : 1 });
	addItem({ name: "item2", value : 2 });
	
	removeItem("item1");

Immutability

The state passed on notification and/or getState() is immutable. Internally, Object.freeze is used to (deeply) freeze the state making it immutable. For large nested states this will have an performance impact (tests will be made to quantify). Due to the use of Object.freeze the overall performance is not directly comparable with, e.g. nanoflux (which does not guarantee immutability), but should be still fast enough for most scenarios.

Asynchronous Actions

nanoflux-fusion supports asynchronous actions out-of-the-box. If a Fusionator returns a promise instead of a state object, the promise will be executed. The state shall be passed as argument of the resolver. Chaining is also possible. nanoflux-fusion aims to support all A+ compliant implementations. It is currently tested with the

Example

function asyncA(arg1){
	return new Promise(function(resolve,reject){
		setTimeout(function(){
			// returns state to be merged
			resolve({a: arg1});
		}, 500)
	})
}
 
function asyncB(arg1){
	return new Promise(function(resolve,reject){
		setTimeout(function(){
			resolve( { b: 5 + arg1.a });
		}, 500)
	})
}

var asyncFusionator = NanoFlux.createFusionator({
	
	simplePromise: function(prevState, args){
			return asyncA(args[0]); 
	},	
	chainedPromises: function(prevState, args){
		return asyncA(args[0]).then(function(data){
			console.log(data); // data = {a: 5} 
			return asyncB(data);  
		});
	}
}, 
// initial state
{
	a: 0,
	b: 0
});

var simplePromise = NanoFlux.getFusionActor("simplePromise");
var chainedPromises = NanoFlux.getFusionActor("chainedPromises");

// call the actions
simplePromise(5); // state will be { a: 5 }
chainedPromises(5); // state will be { a: 5, b: 10 }

Middleware Interface

Since version 0.5 a middleware interface is provided. Simply pass a function object with signature function(newState, oldState, actionName) to the FusionStore.use. The middleware function must return a new state, typically the received newState argument, or a modified version of it (see below).

Note: Nanoflux also provides a middleware interface (Nanoflux.use()), but that interface applies for the dispatcher. In case of Fusion, the dispatcher middleware isn't that useful, as it dispatches always to the same method (i.e. on__fuse()) with internally maintained arguments.

function LoggerMiddleware(){
	var logData = [];

	this.log = function(newState, oldState, actionName){
		logData.push({
		    action: actionName,
		    timestamp: Date.now(),
			state: _.cloneDeep(oldState)
		});

		return newState; // must return a state 
	};

	this.countLogEntries = function(){ return logData.length };
	this.getLogEntry = function(t){
		return logData[t];
	};
}

var fusionStore = NanoFlux.getFusionStore();
var logger = new LoggerMiddleware();
fusionStore.use( logger.log );
// use more if needed ... 

No async support yet

The current middleware implementation is purely synchronous. Of course, you could execute async operations, e.g. server requests, but the middleware won't wait for the request being finished.

New State argument is mutable

The middleware function receives two versions of a state, the new state and the prior/old state before the new state is being merged into the application state. While the old state is immutable, the new state can be modified. That way, the middleware can be used as transformation pipeline.

function addTimestamp(newState, oldState){

		var modifiedState = {};
		modifiedState.modified = Date.now();

		Object.assign(newState, modifiedState);
		return newState;
}

fusionStore.use( addTimestamp );

Compatibility

It should be compatible with all major modern browsers. nanoflux-fusion uses internally Object.assign() and Object.freeze(). While Object.freeze is supported since IE9, Object.assign was only introduced in EDGE. Therefore, you need a polyfill for unsupported browsers, i.e. IE9+.