node package manager

flax

A productive flux implementation to be used with ReactJS

Flax

A productive flux implementation to be used with ReactJS

Flax is based on Flux

  • Productivity, objectivity and clarity
  • Maintain as much freedom as traditional Flux
  • Common flux problems solution proposals

Flax is being developed to address professional level challenges with:

  • Clarity: Flax lets you write Flux in a similar way you would write an app with ReactJS, making it intuitive to learn;
  • Productivity: Flax reduces the common boilerplate while not losing granularity such as the publish-subscribe stores' pattern, but still making simpler syntax possible;
  • Seamlessness: with Flax, for instance, there is little difference between sync and async actions, making action creators easy to implement without additional middleware;
  • Pattern-oriented: alongside with the docs, this repository is being built to also show the best unidirectional dataflow practices to be used with Flax;
  • Developer tools: Flax is being designed to support the creation of user-defined development tools. See flax-devtools, as an example;
import Flax from 'flax';
 
const AppActions = Flax.createActionCreator({
  displayName: 'AppActions',
 
  // Action with empty payload 
  action1() {
    // ... 
    this.dispatch();
  },
  
  // Action with some payload 
  action2(x, y) {
    x++;
    y++;
    this.dispatch({x, y});
  },
  
  // Async action 
  
  /*
  *  We suggest all async requests to be done inside action creators as a form of keeping the stores sync
  */
  action3(x) {
    this.dispatch({loading: true});
    fecth('<url>').then(response => {
      let data = response.json() + x;
      this.dispatch({
        loading: false,
        data
      });
      this.resolve(); // action finished successful 
    }).catch(err => {
      this.dispatch({
        loading: false,
        err
      });
      this.reject(); // action finished with failure 
    });
  }
});
 
export default AppActions;
import Flax from 'flax';
import AppActions from '../actions/AppActions.jsx';
 
const AppStore = Flax.createStore({
  displayName: 'AppStore',
 
  getInitialState() {
    return {
      num1: 0,
      num2: 0,
      loading: false,
      error: null
    };
  },
 
  getActionBinds() {
    return [
      [AppActions.action1, this._handleAction1],
      [AppActions.action2, this._handleAction2],
      [AppActions.action3, this._handleAction3]
    ];
  },
 
  events: {
    NUM1: null,
    NUM2: null,
    LOADING: null,
    ERROR: null
  },
 
  _handleAction1() {
    this.state.num1 = 0;
    this.state.num2 = 0;
    this.emitChange(this.NUM1);
    this.emitChange(this.NUM2);
  },
 
  _handleAction2(payload) {
    this.state.num1 = payload.x;
    this.state.num2 = payload.y;
    this.emitChange(this.NUM1);
    this.emitChange(this.NUM2);
  },
 
  _handleAction3({loading, data, err}) {
    this.state.loading = false;
    if (err) return this.emitChange(this.ERROR);
    if (loading) {
      this.state.loading = true;
      this.emitChange(this.LOADING);
    } else {
      this.state.num1 = data;
      this.emitChange(this.NUM1);
    }
  },
 
  getters: {
    getNum1() {
      return this.state.num1;
    },
 
    getNum2() {
      return this.state.num2;
    },
 
    getLoading() {
      return this.state.loading;
    },
 
    getError() {
      return this.state.error;
    }
  }
  
});
 
export default AppStore;
import React from 'react';
import AppActions from '../actions/AppActions.jsx';
import AppStore from '../stores/AppStore.js';
 
import {
  StoreWatcher
} from 'flax';
 
const App = React.createClass({
  mixins: [StoreWatcher],
 
  getEventBinds() {
    return [
      [AppStore.NUM1, this._handleNum1],
      [AppStore.NUM2, this._handleNum2],
      [AppStore.LOADING, this._handleLoading],
      [AppStore.ERROR, this._handleError]
    ];
  },
 
  getInitialState() {
    return {
      num1: AppStore.getNum1(),
      num2: AppStore.getNum2(),
      loading: AppStore.getLoading(),
      error: AppStore.getError()
    };
  },
 
  _handleNum1() {
    this.setState({
      num1: AppStore.getNum1()
    });
  },
 
  _handleNum2() {
    this.setState({
      num2: AppStore.getNum2()
    });
  },
 
  _handleLoading() {
    this.setState({
      loading: AppStore.getLoading()
    });
  },
 
  _handleError() {
    this.setState({
      error: AppStore.getError()
    });
  },
 
  _handleAction1() {
    AppActions.action1();
  },
 
  _handleAction2() {
    AppActions.action2(1, 2);
  },
 
  _handleAction3() {
    // Action chaining is important to wait for async action 
    AppActions.action3(10).then(() => {
      AppActions.action1();
      AppActions.action2(-1, -2);
    }).catch(() => {
      AppActions.action2(0, 0);
    });
  },
 
  render() {
    let {num1, num2, loading, error} = this.state;
    
    return (
      <div>
        {loading ? <p>Loading...</p> : null}
        {error ? <p>{error}</p> : null}
        <button onClick={this._handleAction1}>Action1</button>
        <button onClick={this._handleAction2}>Action2</button>
        <button onClick={this._handleAction3}>Action3</button>
        <p>{num1}</p>
        <p>{num2}</p>
      </div>
    );
  }
});
 
export default App;

getInitialState could be:

getInitialState() {
  let {num1, num2, loading, error} = AppStore.getState();
  return {num1, num2, loading, error};
},

Or in a cleaner code:

getInitialState() {
  return AppStore.getState();
},

If you want less granularity, the component could be set to listen to all AppStore's events:

getEventBinds() {
  return [
    [AppStore.DEFAULT, this._handleAppChange]
  ];
},

And the store event handles:

_handleAppChange() {
  this.setState(AppStore.getState());
},

MIT