Home

Awesome

Project status

It's unfinished and frozen for now because of existence of a better alternative — Redux

Fluce

Dependency Status devDependency Status Build Status Join the chat at https://gitter.im/rpominov/fluce

Well, Flux again ...

The name is combined from "flux" and "reduce".

Installation

$ npm install fluce

Store

Store in Fluce is just an object with the following shape:

{
  initial: Function,
  reducers: {
    foo: Function,
    bar: Function,
    ...
  }
}

Where initial() returns an initial state, and each of reducers is an action handler called with the current state and the action's payload as arguments returning a new state. Each reducer must be a pure function, that never mutate current state, but returns a new one instead. A reducer's name (e.g. foo above) is the action type that the reducer want to handle.

let myStore = {
  initial() {
    return myInitialState;
  },
  reducers: {
    actionType1(currentStoreState, actionPayload) {
      return computeNewState(currentStoreState, actionPayload);
    },
    actionType2(currentStoreState, actionPayload) {
      /* ... */
    }
  }
};

Fluce instance

You start use Fluce by creating an instance of it. Normally you want only one instance in the browser, but may want to create an instance for each request on the server.

let createFluce = require('fluce/create-fluce');

let fluce = createFluce();

When an instance is created, you can add stores to it:

fluce.addStore('storeName1', myStore1);
fluce.addStore('storeName2', myStore2);

After this is done, you can access each store's current state as fluce.stores.storeName, and dispatch actions with fluce.dispatch('actionType', payload). Also you can subscribe to changes of stores' states:

let unsubscribe = fluce.subscribe(['storeName1', 'storeName2'], (updatedStoresNames) => {
  // you can read new state directly from `fluce.stores.storeName`
});

// later...
unsubscribe();

Callback is called when some of specified stores change their state (via a reducer). If two or more stores change in response to a single action, the callback will be called only once. Also if a reducer returns same state, the store will be considered not changed.

Example

let createFluce = require('fluce/create-fluce');


// Setup

let fluce = createFluce();

fluce.addStore('counter', {
  initial() {
    return 0;
  },
  reducers: {
    counterAdd(cur, x) {
      return cur + x;
    },
    counterSubtract(cur, x) {
      return cur - x;
    }
  }
});

fluce.addStore('counterInverted', {
  initial() {
    return 0;
  },
  reducers: {
    counterAdd(cur, x) {
      return cur - x;
    },
    counterSubtract(cur, x) {
      return cur + x;
    }
  }
});


// Usage

console.log(fluce.stores.counter); // => 0
console.log(fluce.stores.counterInverted); // => 0

fluce.actions.dispatch('counterAdd', 10);

console.log(fluce.stores.counter); // => 10
console.log(fluce.stores.counterInverted); // => -10

fluce.subscribe(['counter', 'counterInverted'], (updated) => {
  console.log('following stores have updated:', updated);
});

fluce.actions.dispatch('counterSubtract', 5);
// => following stores have updated: ['counter', 'counterInverted']

In progress

The features below aren't done yet.

<Fluce /> React component

<Fluce/> is an helper component, you can use to subscribe to stores and fire actions from a component that know nothing about Fluce. It outputs nothing but it's child component to the result DOM. It can have only one child, and renders it with a bit of a magic (adds more props to it).

<Fluce/> accepts following props:

let Fluce = require('fluce/fluce-component');

class Counter extends React.Component {
  render() {

    // Also `this.props.fluce` will be available here,
    // but you shouldn't need it in most cases.

    return <div>
      <button onClick={this.props.onDecrement}>-</button>  
      {this.props.counter}
      <button onClick={this.props.onIncrement}>+</button>
    </div>;
  }
}

function increment(fluce) {
  fluce.dispatch('counterAdd', 1);
}

function decrement(fluce) {
  fluce.dispatch('counterSubtract', 1);
}

class App extends React.Component {
  constructor() {
    render() {
      return <div>
        ...
        <Fluce
          stores={{counter: 'myCounterStore'}}
          actionCreators={{onDecrement: decrement, onIncrement: increment}}
        >
          <Counter />
        </Fluce>
        ...
      </div>;
    }
  }
}

React.render(<Fluce fluce={fluce}><App /></Fluce>, document.getElementById('root'));

And here is an example with custom render function:

<Fluce
  stores={{counter: 'myCounterStore'}}
  actionCreators={{decrement, increment}}
  render={(stores, actionCreators, fluce) => {

    return <Counter
      counter={stores.counter}
      onIncrement={actionCreators.increment}
      onDecrement={actionCreators.decrement}
    />;

  }}
/>

Internally we use context to pass fluce instance through components tree, and React.addons.cloneWithProps to add props to child component.

Optimistic dispatch

Thanks to pure action handlers we can support optimistic dispatch of actions. An optimistic dispatch can be canceled, in this case we simply roll back to the state before that action, and replay all actions except the canceled one.

fluce.addActionCreator('fooAdd', (fluce) => {
  return (foo) => {
    let action = fluce.optimisticallyDispatch('fooAdd', foo);
    addFooOnServer(foo)
      .then(
        // To confirm an optimistic dispatch is as important as to cancel,
        // because before it confirmed we have to collect
        // all actions (with payloads) that comes after the action in question.
        () => action.confirm(),
        () => action.cancel()
      );
  };
});