Understanding how to implement a Flux architecture using Flummox

1k views Asked by At

I currently have a very simple React app that uses state to determine a couple of views. Clicking on the submit element fires a simple call to an endpoint passing either login or registration credentials. I could continue down this road to use React and JSON returned from the server to determine my views, but I am aware this is not proper the way to handle data.

My question is, from here, how do I implement a Flux pattern using Flummox? Do I fire an action with the payload and make my API call in the action or the store? I'm trying to understand the proper way to make this roundtrip from an action in my UI, to my API call, to getting the response and the UI responding to a change on the store.

Below is code for my Auth component. I would like to see an example on how to use Flummox in a situation as simple as this, so I can understand what's needed to scale and add features.

Auth.jsx

'use strict';

var React  = require('react');
var request = require('request');

var Auth = React.createClass({

  getInitialState: function() {
    return {
      name: '',
      email: '',
      pass: '',
      passConfirm: '',
      login: true,
      register: false
    };
  },

  handleLogin: function(){
    this.setState({
      login: true,
      register: false
    });
  },

  handleRegister: function(){
    this.setState({
      register: true,
      login: false
    });
  },

  handleName: function(event){
    this.setState({
      name: event.target.value
    });
  },

  handleEmail: function(event){
    this.setState({
      email: event.target.value
    });
  },

  handlePass: function(event){
    this.setState({
      pass: event.target.value
    });
  },

  handlePassConfirm: function(event){
    this.setState({
      passConfirm: event.target.value
    });
  },

  handleSubmit: function(){

    var register = {
      name: this.state.name,
      email: this.state.email,
      password: this.state.pass,
      confirmPassword: this.state.passConfirm
    };

    var endpoint = 'http://localhost:3000';

    request({
       uri: endpoint + '/register',
       method: 'POST',
       json: register
   }, function(error, response, body){
       if (error) {
         console.log(error);
       } else {
         console.log(response);
       }
   });
  },

  renderLogin: function () {
    return (
      <form className="login">
        <div className="input-container">
          <input type='text' value={this.state.email} onChange={this.handleEmail} placeholder="email" />
          <input type='password' value={this.state.password} onChange={this.handlePass} placeholder="password" />
        </div>

        <div className="auth-submit" onClick={this.handleSubmit}>Login</div>
      </form>
    );
  },

  renderRegister: function(){

    return (
      <form className="register">
        <div className="input-container">
          <input type='text' value={this.state.name} onChange={this.handleName} placeholder="name" />
          <input type='email' value={this.state.email} onChange={this.handleEmail} placeholder="email" />
          <input type='password' value={this.state.pass} onChange={this.handlePass} placeholder="password" />
          <input type='password' value={this.state.passConfirm} onChange={this.handlePassConfirm} placeholder="confirm password" />
        </div>

        <div className="auth-submit" onClick={this.handleSubmit}>Register</div>
      </form>
    );
  },

  render: function(){

    var auth = null;
    if (this.state.login) {
      auth = this.renderLogin();
    } else if (this.state.register) {
      auth = this.renderRegister();
    }

    return (
      <div className="auth-container">
        <div className="auth-logo">Flow</div>
        {auth}
        <div className="auth-select">
          <div className="auth-label-container">
            <div className="auth-label" onClick={this.handleLogin}>Login</div>
          </div>
          <div className="auth-label-container">
            <div className="auth-label" onClick={this.handleRegister}>Register</div>
          </div>
        </div>
      </div>
    );
  },
});

module.exports = Auth;
1

There are 1 answers

1
julen On BEST ANSWER

Before diving into Flux, I'd suggest to clean up the existing React code you have.

As a start I would try to reduce component's own state as much as possible. For example, instead of having separate state for login and register, you can have a single screen as state, which accepts the string value of the actual screen/view you are rendering (login/register).

Also each renderLogin() and renderRegister() are better off as components on their own. Therefore your Auth component would end up being a controller-view.

Form handling looks too verbose at the same time: you have defined a separate handler for every single field, but a single handleChange() would suffice (you can set name="<state-field-name>" in your input elements to reference the state field you are referencing).


Afer that you can start building the flux pattern using Flummox and moving your app state (and not local component state) into stores.

I assume you have read the docs, but as a summary Flummox offers Store and Action helpers to model your application's data (source of truth) and UI actions.

The actions module could look something like this:

import { Actions } from 'flummox';


export default class AuthActions extends Actions {

  gotoScreen(screenName) {
    return screenName;
  }

  register(requestBody) {
    // Here you deal with your authentication API,
    // make async requests and return a promise.
    // This part is specific to your backend.
    return AuthAPI.register(requestBody)
                  .then(
                    (value) => Promise.resolve(value),
                    (reason) => Promise.reject(reason)
                  );
  }

}

And an AuthStore will be the source of truth, i.e. handles the actual app state. This registers with actions, so it knows how to update itself whenever a change is emitted by actions.

import { Store } from 'flummox';


export default class AuthStore extends Store {

  constructor(flux) {
    super();

    let authActions = flux.getActions('auth');

    // Here you register the functions which will take care
    // of handling the actions and update the store's state.
    // These can be sync or async.
    this.register(authActions.gotoScreen, this.handleGotoScreen);
    this.registerAsync(authActions.register, this.handleRegisterBegin,
                                             this.handleRegisterSuccess,
                                             this.handleRegisterError);

    // This is the initial state of your flux store
    this.state = {
      screen: 'login',
      // ... any extra state your app might need
    };
  }


  /* Screen handling */

  handleGotoScreen(screenName) {
    this.setState({screen: screenName});
  }

  /* Register */

  handleRegisterBegin(requestBody) {
    this.setState({
      // change state based on action
    });
  }

  handleRegisterSuccess() {
    this.setState({
      // change state based on action
    });
  }

  handleRegisterError(errors) {
    this.setState({
      // change state based on action
    });
  }

}

These need to be encapsulated in a Flux object which can then be referenced from anywhere in your app.

import { Flummox } from 'flummox';

import AuthActions from './AuthActions';
import AuthStore from './AuthStore';


export default class Flux extends Flummox {

  constructor() {
    super();

    this.createActions('auth', AuthActions);
    this.createStore('auth', AuthStore, this);
  }

}

But how do your views know about store's state? The preferred way to glue your stores with views is by using the FluxComponent, which would receive the store's state via props when using FluxComponent.

FluxComponent receives an instance of your app's Flux object, and you also need to specify which stores it wants to connect to.

import Flux from './Flux';
let flux = new Flux();

<FluxComponent flux={flux} connectToStores={['auth']}>
  <Auth />
</FluxComponent>

The state is passed as props, and you can reference the values directly as you do with props normally.

At the same time, this allows to access the flux prop in the underlying views, which gives us access to actions, and therefore are able to trigger them when user interaction happens:

// You can fire actions from anywhere in your component
this.props.flux.getActions('auth').gotoScreen('register')

With this finishes (or begins) the flux cycle.