Skip to content
Tomáš Holas edited this page Jun 27, 2015 · 2 revisions

Everybody's doing todos these days, so I've decided to do something different, albeit maybe a bit less useful. We'll be building a film database, with a 1:N relation between directors and films. User authentication baked in. This is not a step-by-step guide, rather a showcase of the most interesting bits of code. If you want to dive right in, the whole application code is available on github. Also, to have a general idea how it looks, you can try out the application running on Heroku.

Backend

For me, the starting point of a web application is a REST api. For this task, we'll use node.js, koa middleware library and MongoDB database. First, let's define models using Mongoose ODM.

Models

We need a user model, to allow users to sign up and create other objects:

// server/models/user.js
import mongoose from 'mongoose';
import jsonSelect from 'mongoose-json-select';

const UserSchema = new mongoose.Schema({
  username: {
    type: String,
    required: true,
    index: {
      unique: true
    }
  },
  password: {
    type: String,
    required: true
  },
  token: String
});

UserSchema.plugin(jsonSelect, '-__v -password');

export default mongoose.model('user', UserSchema);

Attribute username has unique index to disallow duplicate usernames on the ODM layer. Both username and password are set to required. Generated token will be used for authentication. the jsonSelect plugin serves to remove password from the REST api.

Then we have the director model. Interesting column is user, which is a reference to user model, and allows us to have separate data for, well, each registered user. The createdAt column is auto-populated when the model is being saved to the database.

// server/models/director.js
import mongoose from 'mongoose';
const DirectorSchema = new mongoose.Schema({
    name: String,
    nationality: String,
    birthday: Date,
    biography: String,
    user: {
      type: mongoose.Schema.Types.ObjectId,
      ref: 'user'
    },
    createdAt: {
      type: Date,
      default: Date.now
    }
  });

export default mongoose.model('director', DirectorSchema);

The film model is very similar to director, and also contains a reference to the director object which "owns" the film.

// server/models/film.js
import mongoose from 'mongoose';
const FilmSchema = new mongoose.Schema({
  name: String,
  director: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'director'
  },
  description: String,
  year: Number,
  user: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'user'
  },
  createdAt: {
    type: Date,
    default: Date.now
  }
});

export default mongoose.model('film', FilmSchema);

API

And now, with the virtue of koa-mongo-rest, the REST CRUD api including search, sort, skip and limit, will just generate itself. I admit, this is not so unique feature now that we have LoopBack, but if you want to use koa instead of express, and don't want to get yourself locked in a big framework, it's a way to go. To see the details of the api, take a look at koa-mongo-rest documentation.

// server/koa.js
import koa from 'koa';
import mongoose from 'mongoose';
import koaRouter from 'koa-router';
import generateApi from 'koa-mongo-rest';

const app = koa()
// Read Heroku production Mongo urls
const mongoUrl = process.env.MONGOHQ_URL || process.env.MONGOLAB_URI || '127.0.0.1:27017/isofilmdb';

mongoose.connect(mongoUrl);
app.use(koaRouter(app));

generateApi(app, Film, '/api');
generateApi(app, Director, '/api');

Authentication

Of course, we can't use the generic CRUD api for user registration and login, as those are very specific actions. Thanks to koa though, executing a sequence of asynchronous operations with proper error handling has become fun again.

The register method creates a user account, with a properly hashed and salted password, and sets the token, which will be later used with API authentication. The only catch here is, bcrypt library doesn't return promises, but it's methods expect the last argument to be a callback. The co library used in koa doesn't support those calls with yield, but only supports so called thunks, which are functions with only one argument, which is the callback. Fortunatelly, it's pretty easy to convert a function to a thunk by partially applying the function using the bind keyword. Of course, we could use a promisified version of bcrypt, but thath would be half the fun. Then, User.create from mongoose returns a promise, so there's no need to modify that one. With the yield keyword, we can use classic try/catch block, so error handling is easy as well. Here, the error is actually thrown by mongoose, when the validation of User unique username index fails.

app.post('/auth/register', function *(next) {
  yield next;
  const SALT_WORK_FACTOR = 10;
  const error = {message: 'Username already exists'};
  try {
    const body = this.request.body;
    const salt = yield bcrypt.genSalt.bind(null, SALT_WORK_FACTOR);
    const hash = yield bcrypt.hash.bind(null, body.password, salt);
    body.password = hash;
    body.token = uuid.v1();
    const result = yield User.create(body);
    this.status = 201;
    this.body = result;
  } catch (err) {
    this.status = 409;
    this.body = error;
  }
});

To succesfully login, we need to find user by username first, and then compare the hashed password using bcrypt. We are also going to reset the token on each login. Updated user data are send as a response when login is successful.

app.post('/auth/login', function *(next) {
  yield next;
  try {
    const body = this.request.body;
    const error = {message: 'Username and password doesn\'t match'};
    const user = yield User.findOne({
      username: body.username
    });
    if (!user) throw error;
    const match = yield bcrypt.compare.bind(null, body.password, user.password);
    if (!match) throw error;
    user.token = uuid.v1();
    this.status = 201;
    this.body = yield user.save();
  } catch (err) {
    this.status = 401;
    this.body = err;
  }
});

When user successfuly logs in, he gets token. To be able to access the api, the token must be set in request header auth-token. Following middleware is used to prevent access to api unless user if found in database by supplied token. Notice the destructuring assignment User.findOne({token}). The middleware also augments the incoming request so that only data for this specific user are returned by subsequent handlers (which are generated by koa-mongo-rest). For GET requests, user's id is added to query.conditions and for other requests, the id is added to the request body.

app.use(function *(next) {
  const token = this.req.headers['auth-token'];
  const isApi = !!this.request.url.match(/^\/api/);
  const user = token && (yield User.findOne({token}));
  if (isApi && !user) {
    this.status = 401;
    this.body = '401 Unauthorized';
    return;
  }
  this.request.user = user;
  if (user) {
    // Add user to get condition for API
    if (this.request.method === 'GET') {
      var conditions;
      var query = clone(this.request.query);
      try {
        conditions = (query.conditions && JSON.parse(query.conditions)) || {};
      } catch (err) {
        console.error(err);
        conditions = {};
      }
      conditions.user = user._id;
      query.conditions = JSON.stringify(conditions);
      this.request.query = query;
    }
    // Add user to post data for API
    else if (this.request.body) {
      this.request.body.user = user._id;
    }
  }
  yield next;
});

Server-side rendering

Now that the api is done, there is one last thing we need to deal with on the backend - the server side rendering of React application. The core of this functionality is provided by React.renderToString. Whole React application is included from the frontend, and proper page is rendered on server. This way, we don't need to wait for the client-side JavaScript to run and create the markup - the markup is pre-created on the server for the accessed url, and directly shown in the browser. When the client JavaScript runs, it picks up where server has left. As we are using the alt flux library, we also need to initialize flux on the server using alt.bootstrap. It is optionally possible to prefill the flux stores with data for rendering, but we skip that part. It is also necessary to decide which component to render for which url. That is the functionality of the client side Router, which we will show later in detail. We are using the singleton version of alt, so after each render, we need to alt.flush() the stores to have them clean for another request. Usin the iso addon, the state of flux is serialized to the html markup, so that the client knows where to pick up.

// server.js
app.use(function *(next) {
  // We seed our stores with data
  alt.bootstrap(JSON.stringify({}));
  var iso = new Iso();

  // We use react-router to run the URL that is provided in routes.jsx
  const handler = yield Router.run.bind(this, routes, req.url);
  const content = React.renderToString(React.createElement(handler));

  iso.add(content, alt.flush());
  res.render('layout', {html: iso.render()});
})

Frontend

Finally, we are gettin to the frontend part of our application. Aside from react, we are going to use a few other libraries, including:

First thing we need to do on the fronted is to pickup the server state, and bootstrap alt with the data. Then we run Router and React.render to target container, which will update the server-generated markup as necessary.

Iso.bootstrap(function (state, _, container) {
  // bootstrap the state from the server
  alt.bootstrap(state)
  Router.run(routes, Router.HistoryLocation, function (Handler, req) {
    let node = React.createElement(Handler)
    React.render(node, container)
  })
})

Routing

As for the routing library, we are using react-router, which comes with handy JSX-based routes definition language:

import React from 'react';
import {Route, DefaultRoute, NotFoundRoute} from 'react-router';

export default (
  <Route name='app' path='/' handler={require('./components/app')}>
    <DefaultRoute
      name='directors'
      path='directors'
      handler={require('./components/directors/directors-table')} />
    <Route
      name='login'
      path='login'
      handler={require('./components/login')} />
    <Route
      name='films'
      path='films'
      handler={require('./components/films')} />
    <NotFoundRoute handler={require('./pages/not-found')} />
  </Route>
);

Each route needs to specify name, path and handler component. Pretty straightforward. react-router has support for nested routes applied to nested components, which allows for a sort of template inheritance. In our application though, we are using only a single level of depth:

@reactMixin.decorate(Router.State)
export default class App extends React.Component {
  render() {
    var navbar;
    if (this.getPathname() !== '/login') {
      navbar = <Navbar />;
    }
    return (
      <div className="container-fluid">
        {navbar}
        <RouteHandler />
        <Footer />
      </div>
    );
  }
}

We have just optional Navbar in header, RouteHandler as main content, and a Footer. Navbar is visible on all pages, except login. Note the @reactMixin.decorate(Router.State) decorator. So far, mixins were one of the favourite ways of reusing functionality in React, and many libraries depend on it. react-router is no exception. Alas, mixins have recently gone out of fashion. You cannot use mixins in ES6 classes. It's true, it would be a shame to implement yet another proprietary way of augmenting classes, when we already have decorators. And with the react-mixin library, you can just drop-in the good old mixin into the decorate function, and magically, you have mixins back. They might be on the way out, but for the present, we need a way to use them until they are rewritten into something else. For this particular Router.State mixin, it has a rather simple function - it extend out component with path awareness, including the getPathname function.

Now that we have the basic structure of the frontend application, we can proceed to creating actual components. According to react-router authors, this is the correct way of reasoning about a web applications:

Make the URL your first thought, not an after-thought.

Flux

But before jumping into components, let's take a quick flux refresh. Facebook's flux documentation speaks a lot about dispatcher, but we are free to skip that, because in alt, dispatcher is implicitly wired to actions by convention, and usually doesn't require any custom code. This leaves us with just stores, actions and components. Also, those 3 layers can be used in such a way, that maps nicely into existing MVC thought model: Stores are Model, actions are Controller, and components are View. The main difference is the uni-directional data flow, which means, that controllers(actions) cannot directly modify views(components), but they can just trigger model(store) modifications, to which views are passively bound. This was already a best practice of some enlightened Angular developers.

     ┌───────────────────────────────────────┐                                
     │                                       │                                
     ▼                                       │                                
 ┌────────┐         ┌───────┐          ┌─────┴─────┐                          
 │ Action │────────▶│ Store │─────────▶│ Component │                          
 └────────┘         └───────┘          └───────────┘                          

The workflow is as follows:

  • Components initiate actions
  • Stores listen to actions and update data.
  • Components are bound to stores and rerender when data are updated

Application is divided to "slices", where each slice roughly corresponds to one page or use case. For each slice, we have it's corresponding actions, stores and components. There can be dependencies between slices. The order in which to describe each slice is usually to start with actions, continue with store, and last (but not least) the components.

Login page

When using alt flux library, actions generally come in 2 flavors - automatic and manual. Automatic actions are created using the generateActions function and they go directly to the dispatcher. Manual methods are defined as methods of your actions class and they can go to dispatcher with additional payload. Most common use case of automatic actions is to just notify stores about some event in the application. Manual actions are, among other thing, the preffered way of dealing with server interactions.

So the REST api calls belong to actions. The complete workflow is as follows:

  • Component triggers an action
  • Action creator runs async server request, and the result goes to dispatcher as payload
  • Store listens to action, action handler receives result as an argument, store updates its state accordingly.

For AJAX requests, we use the axios library, which among other things, deals with JSON data and headers seamlessly. Instead of promises or callbacks, we use the ES7 async/await. If the POST response is not 2XX, an error is thrown, and we dispatch either returned data, or received error.

class LoginActions {
  constructor() {
    this.generateActions('logout', 'loadLocalUser');
  }
  async login(data) {
    try {
      const response = await axios.post('/auth/login', data);
      this.dispatch({ok: true, user: response.data});
    } catch (err) {
      console.error(err);
      this.dispatch({ok: false, error: err.data});
    }
  }
  async register(data) {
    try {
      const response = await axios.post('/auth/register', data);
      this.dispatch({ok: true, user: response.data});
    } catch (err) {
      console.error(err);
      this.dispatch({ok: false, error: err.data});
    }
  }
}

module.exports = (alt.createActions(LoginActions));

Flux store serves 2 purposes - it has action handlers and state. LoginStore has 2 state attributes - user for currently logged in user and error for current login related error. In the spirit of reducing boilerplate, alt allows us to bind to all actions from one class with a single function bindActions.

class LoginStore {
  constructor() {
    this.bindActions(LoginActions);
    this.user = null;
    this.error = null;
  }

Handler names are defined by convention prepending on to action name, so the login actions is handled by onLogin, and so forth. Note that the first letter of the action name will be capitalized to preserve camelCase.

In our LoginStore we have following handlers:

  onLogin(data) {
    this.handleUser(data);
  }
  onRegister(data) {
    this.handleUser(data);
  }
  onLogout() {
    this.clearUser();
    this.redirectToLogin();
  }
  onLoadLocalUser() {
    var user;
    try {
      user = JSON.parse(localStorage.getItem('filmdbUser'));
    } finally {
      user && this.storeUser(user);
    }
  }

Handlers onLogin, onRegister and onLogout are triggered by user action, while onLoadLocalUser is triggered on application load to get user data from localStorage. This is essential, because flux stores are of course cleared with a page refresh, and we need to maintain users session somewhere. Preferably NOT in a cookie, because cookie authentication is not supported with native mobile apps. We want our api device agnostic, right?

Those handlers make use of following helper methods:

  handleUser(data) {
    if (data.ok) {
      this.storeUser(data.user);
      this.redirectToHome();
    }
    else {
      this.clearUser();
      this.error = data.error.message;
      this.redirectToLogin();
    }
  }
  storeUser(user) {
    this.user = user;
    this.error = null;
    api.updateToken(user.token);
    localStorage.setItem('filmdbUser', JSON.stringify(user));
  }
  clearUser() {
    this.user = null;
    api.updateToken(null);
    localStorage.removeItem('filmdbUser');
  }
  redirectToHome() {
    defer(router.transitionTo.bind(this, 'directors'));
  }
  redirectToLogin() {
    defer(router.transitionTo.bind(this, 'login'));
  }
}

module.exports = (alt.createStore(LoginStore));

handleUser either calls storeUser and redirects to home page, or calls clearUser, sets login error and redirects back to login page. As per storeUser and clearUser, user information needs to be maintained in 3 places - LoginStore state this.user, api token in headers, and localStorage. Redirects are being called on next tick with lodash's defer as not to violate the single-dispatch-at-time rule. Now we can proceed to the login screen component. This component uses 2 decorators - connectToStores from alt to bind the component to stores and our own changeHandler to streamline working with inputs, which involves a bit more work without 2-way data-binding.

connectToStores expects 2 static methods on our class. Why those methods need to be static? That't because connectToStores is a class decorator, and it's code needs to execute before we create any instance.

The first method getStores will be called exactly once, and only returns the array of stores to which our component will listen for changes. Listening and un-listening is handled by alt.

The second method getPropsFromStores is being called on each store change, and returns an object that will be merged to this.props. Under the hood, this happens through wrapping our component into higher order component.

We also need to define a login object on this.state, which will serve as a container object for login and password from form inputs. It's a better practice to have related inputs contained in a separate data object than just setting their value directly to this.state.

For buttons, we are defining register and login handlers, which will just pass this.state.login object to appropriate action creator.

import {Input, Button, Alert} from 'react-bootstrap';
import connectToStores from 'alt/utils/connectToStores';

@connectToStores
@changeHandler
export default class LoginPage extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      login: {}
    };
  }
  static getStores() {
    return [LoginStore];
  }
  static getPropsFromStores() {
    return LoginStore.getState();
  }
  register() {
    LoginActions.register(this.state.login);
  }
  login() {
    LoginActions.login(this.state.login);
  }

// Now let's inspect the markup. As we don't want to bother too much witch CSS, we are using the ubiquitous [bootstrap](http://getbootstrap.com/) framework, along with it's [react-bootstrap](http://react-bootstrap.github.io/) counterpart, which reimplements bootstrap's jQuery plugins to native react components.

The error sub-component is show only in case of error. This is accomplished in the render method by defining the sub-component when error is set, and otherwise leaving undefined. The error subcomponent is inserted into the JSX markup as {error}. The onChange and onClick handlers need to use bind to set the context correctly, as methods in ES6 classes are not autobound.

  render() {
    var error;
    if (this.props.error) {
      error = <Alert bsStyle="danger">{this.props.error}</Alert>;
    }
    return (
    <div className="container">
      <div className="jumbotron col-centered col-xs-10 col-sm-8 col-md-7 ">
        <h1>FilmDB</h1>
        <p className="lead">Watch This™</p>
        <h2>Login or create account</h2>
        <br/>
        {error}
        <Input
          label='Username'
          type='text'
          value={this.state.login.username}
          onChange={this.changeHandler.bind(this, 'login', 'username')} />
        <Input
          label='Password'
          type='password'
          value={this.state.login.password}
          onChange={this.changeHandler.bind(this, 'login', 'password')} />
        <Button bsStyle="danger" onClick={this.register.bind(this)}>Create account</Button>
        <Button bsStyle="success" className="pull-right" onClick={this.login.bind(this)}>Sign in</Button>
      </div>
    </div>
    );
  }
}

The JavaScript's bind expression is also used when calling changeHandler to define it's first 2 parameters, which are data object key on state, and attr to which the input's value should be saved. The changeHandler decorator just defines the changeHandler function on targer class prototype. As an example, when this handler is called with ('login', 'username', 'foo'), it sets this.state.login.username = 'foo'. It's quite handy to be able to reuse this code across many components, because it's needed quite often, and not fun to write all over again.

function changeHandler(target) {
  target.prototype.changeHandler = function(key, attr, event) {
    var state = {};
    state[key] = this.state[key] || {};
    state[key][attr] = event.currentTarget.value;
    this.setState(state);
  };
  return target;
}

API tools

It would be possible to just use axios manually all the time, but it often makes sense to encapsulate the api related information into an object, which will make calling actual requests more succint. For this, we are going to use restful.js library, which defines a few common idioms for accessing REST endpoints. I allows us to specify server information, and then define REST collections. On those collections, we can call all 4 HTTP verbs to performs api calls. We can also set the auth-token application-wide. From the rest of the application, we are just going to use api.films, api.directors, and so forth.

import restful from 'restful.js';

const api = {};

if (process.env.BROWSER) {
  const hostname = window.location.hostname;
  const protocol = window.location.protocol.replace(':', '');
  const port = window.location.port;
  const server = restful(hostname)
    .prefixUrl('api')
    .protocol(protocol)
    .port(port);

  api.server = server;
  api.films = server.all('films')
  api.directors = server.all('directors')
  
  api.updateToken = function (token) {
    server.header('auth-token', token);
  };
}

export default api;

This setup along with ES7 async/await makes creating actions a breeze:

  async update(id, data) {
    const response = await api.directors.put(id, data);
    this.dispatch(response().data);
  }

After I've written a few of those actions, a common pattern emerged. For each network action I needed to implement error handling, and also some status indicator to show to the user that some data are loading. For this, we use StatusActions and StatusStore that listens to them and sets the state of network requests application-wide. Also, when api responds with 401, we want to force logout. StatusActions.failed comes with additional payload to allow retry functionality when network reconnects, which is neat.

async function networkAction(context, method, ...params) {
  try {
    StatusActions.started();
    const response = await method.apply(context, params);
    context.dispatch(response().data);
    StatusActions.done();
  } catch (err) {
    console.error(err);
    if (err.status === 401) {
      LoginActions.logout();
    }
    else {
      StatusActions.failed({config: err.config, action: context.actionDetails});
    }
  }
}

This function is then reused in action creators to make the definition of api calls super-short:

class FilmsActions {
  get(id) {
    networkAction(this, api.directors.get, id);
  }
  add(data) {
    networkAction(this, api.directors.post, data);
  }
  update(id, data) {
    networkAction(this, api.directors.put, id, data);
  }
}

Network status handling

All status actions are automatic, because they don't need to do any other stuff than just deliver update to the StatusStore.

class StatusActions {
  constructor() {
    this.generateActions('started', 'done', 'failed', 'retry');
  }
}

module.exports = (alt.createActions(StatusActions));

The StatusStore holds the information about current network request status, whether the netowrk is busy and if there were an error. Action handlers set those according to the request state. The interesting part is the onRetry handler, which uses the information from failed action, and retries it using axios. Here we also have the rare opportunity to use alt dispatcher directly, because we are not inside an action - we are dispatching whatever action previously failed. On this example we see that the Convention Over Configuration approach that alt uses does not sacrifice flexibility. A common misconception indeed.

class StatusStore {
  constructor() {
    this.bindActions(StatusActions);
    this.busy = false;
    this.error = false;
  }
  onStarted() {
    this.busy = true;
    this.error = false;
  }
  onDone() {
    this.busy = false;
    this.error = false;
  }
  onFailed(retryData) {
    this.busy = false;
    this.error = true;
    this.retryData = retryData;
  }
  async onRetry() {
    const response = await axios(this.retryData.config);
    var data = response.data;
    alt.dispatch(this.retryData.action.symbol, data, this.retryData.action);
    StatusActions.done();
  }
}

module.exports = (alt.createStore(StatusStore));

Navbar

We are now ready to fully exploit the network status information in the Navbar. Aside from the usual components from react-bootstrap, we are also using the Link component from react-router, which allows us to specify routes using their router names instead of url. Using urls directly is sometimes considered a bad practice. To make the matter even easier for a lazy person, we've also included NavItemLink from react-router-bootstrap, which takes care of automatically setting the active class on currently active route.

Navbar also includes 3 optional sub-components. errorComponent is an alert shown when there's an error, busyComponent is a spinner and retryComponent is a button that will trigger the retry action.

import {Link} from 'react-router';
import {NavItemLink} from 'react-router-bootstrap';
import {Alert, Button} from 'react-bootstrap';

@connectToStores
export default class Navbar extends React.Component {
  static getStores() {
    return [StatusStore];
  }
  static getPropsFromStores() {
    return StatusStore.getState();
  }
  retry() {
    StatusActions.retry();
  }
  logout() {
    LoginActions.logout();
  }
  render() {
    var errorComponent;
    var retryComponent;
    var busyComponent;
    if (this.props.error) {
      if (this.props.retryData) {
        retryComponent = <Button onClick={this.retry} bsStyle="danger" bsSize="xsmall" className="pull-right">Retry</Button>;
      }
      errorComponent = (
      <Alert bsStyle='danger'>
      <strong>Network Error!</strong>
      {retryComponent}
      </Alert>);
    }
    if (this.props.busy) {
      busyComponent = <div className="busy-indicator pull-right"><i className="fa fa-refresh fa-spin"></i></div>;
    }
    return (
      <div>
        <nav className="navbar navbar-default">
          <div className="container-fluid">
            <div className="navbar-header">
              <Link to='app' className="navbar-brand">
                <i className="fa fa-film"></i> FilmDB
              </Link>
            </div>
            <ul className="nav navbar-nav">
              <li>
                <NavItemLink to='directors'>Directors</NavItemLink>
              </li>
              <li>
                <NavItemLink to='films'>Films</NavItemLink>
              </li>
            </ul>
            <ul className="nav navbar-nav pull-right">
              <li onClick={this.logout.bind(this)}>
                <a href="#">Logout</a>
              </li>
            </ul>
            {busyComponent}
          </div>
        </nav>
        {errorComponent}
      </div>
    );
  }
}

Directors

It is interesting, how much effort it takes in a modern single page application before one gets to the actual content. The Directors "slice" is about CRUD operations on a collection of directors.

The DirectorsActions are quite self-explaining. Notice that the data argument needs to be cloned for add and update actions. That is because of the retry functionality introduced earlier. If we didn't clone the object and just passed it by reference, it would later get overwritten.

actions

import {clone} from 'lodash';

class DirectorsActions {
  fetch() {
    networkAction(this, api.directors.getAll);
  }
  add(data) {
    networkAction(this, api.directors.post, clone(data));
  }
  update(id, data) {
    networkAction(this, api.directors.put, id, clone(data));
  }
  delete(id) {
    networkAction(this, api.directors.delete, id);
  }
}

module.exports = (alt.createActions(DirectorsActions));

The DirectorsStore stores data from above actions, but also needs to do some processing. When we fetch the collection from api, we then store it in this.directors. But we also need to create directorsHash, which will be used by Films page to fill in director data for the 1:N relation. Film objects store only director's _id, and in would be inefficient to fetch director objects from database for each displayed film extra. There's a library normalizr that expands a bit on this approach, but for us one hash is sufficient. We are using functional approach to creating this hash. The reduce function keeps the first parameter, initialized with {} over all iterations, and all items of the directors array are stored under their _ids.

In some applications it is possible to handle adding, updating or deleting items by reloading whole collection from server afterwards, but that would go against the flux store philosophy, and would have worse performance of course. Adding is easy - just push the newly saved item to the end of the collection (at least in our use case). For updating and deleting, we need to identify which ite in out collection to update/delete depending on the id of the element returned from action.

import {assign} from 'lodash';
import {findItemById, findIndexById} from 'utils/store-utils';

class DirectorsStore {
  constructor() {
    this.bindActions(DirectorsActions);
    this.directors = [];
    this.directorsHash = {};
  }
  onAdd(item) {
    this.directors.push(item);
  }
  onFetch(directors) {
    this.directors = directors;
    this.directorsHash = this.directors.reduce((hash, item) => {
      hash[item._id] = item;
      return hash;
    }, {});
  }
  onUpdate(item) {
    assign(findItemById(this.directors, item._id), item);
  }
  onDelete(item) {
    this.directors.splice(findIndexById(this.directors, item._id), 1);
  }
}

module.exports = (alt.createStore(DirectorsStore));

For this task of finding an element (or it's index) by id we've created two helper methods. Curiously, returning the whole element is a bit easier than it's index. Of course, array.indexOf is not an option, because the object just returned from api is not the same instance.

function findItemById (collection, id) {
  return collection.find(x => x._id === id);
}
function findIndexById(collection, id) {
    var index;
    collection.find((x, i) => {
      index = i;
      return x._id === id;
    });
    return index;
  }
}

Before we start with the component, we are going to need yet another decorator - this time for authentication:

function requireUser(target) {
  target.willTransitionTo = function(transition) {
    if (!localStorage.filmdbUser) {
      transition.redirect('/login');
    }
  };
  return target;
}

The static method willTransitionTo is being used by react-router to optionaly redirect before the route is changed. We are checking for user in localStorage, and redirect to login if it's not present. Of course it would be possible to add some additional security features, but failed api call is going to redirect as well, so for now we are safe enough. It might be also cleaner to check for the user in LoginStore, but willTransitionTo happens so early in the application lifecycle that the loadLocalUser action is not finished yet. I'm waiting for suggestions how to fix this properly.

import {ModalTrigger, Button} from 'react-bootstrap';
import moment from 'moment';

@requireUser
@connectToStores
export default class DirectorsTable extends React.Component {
  static getStores() {
    return [DirectorsStore];
  }
  static getPropsFromStores() {
    return DirectorsStore.getState();
  }

After defining the connectToStores dependencies, we are going to call fetch action in constructor to load data from api. The workflow is as usual - fetch data in action, store listen and updates it's state, this component is bound to the store state, so the change triggers re-render.

  constructor(props) {
    super(props);
    this.state = {};
    DirectorsActions.fetch();
  }
  add() {
    this.refs.modalTrigger.props.modal.props.editItem = null;
    this.refs.modalTrigger.show();
  }

We have only one action handler on this page, and that is for the Add button to show the modal with the edit form. Form is used for editing as well, so we need to clear the editItem.

  render() {
    return (
      <div className="container-fluid">
        <h1>Directors</h1>
        <ModalTrigger
          ref="modalTrigger"
          modal={<DirectorForm />}>
          <span/>
        </ModalTrigger>
        <Button bsStyle="primary" bsSize="large" onClick={this.add.bind(this)}>Add new director</Button>
        <br/>
        <table className="table table-striped item-table">
          <thead>
            <tr>
              <th>Name</th>
              <th>Nationality</th>
              <th>Birthday</th>
              <th>Biography</th>
              <th>Action</th>
            </tr>
          </thead>
          <tbody>

Iterating over array of directors to render the table lines is accomplished using the map function. We are just inlining an array of JSX components. Actually, it feels much better to use plain JS for iteration instead of having to devise specialized templating language.

            {this.props.directors && this.props.directors.map((item, index) =>
            <tr key={index}>
              <td>{item.name}</td>
              <td>{item.nationality}</td>
              <td>{moment(item.birthday).format('D MMMM YYYY')}</td>
              <td className="ellipsis">{item.biography}</td>
              <td>

For row actions, we have a special component ActionBar that is shared among multiple tables. It's being passed some rather assorted properties, but it works for this use case. Current item object, the deleteAction to call on delete click, and the modalTrigger to be called on edit click.

                <ActionBar
                  item={item}
                  deleteAction={DirectorsActions.delete}
                  modalTrigger={this.refs.modalTrigger}/>
              </td>
            </tr>
            )}
          </tbody>
        </table>
      </div>
    );
  }
}

It's very easy to create components in React, and so you are encouraged to create as many of them as possible, and keep them small. Even a set of 2 buttons is worth its own component. As for functionality, on delete click we just execute the deleteAction passed in props and on edit we are setting the item into "modalTrigger's modal's" editItem property.

export default class ActionBar extends React.Component {
  delete() {
    this.props.deleteAction(this.props.item._id);
  }
  edit() {
    this.props.modalTrigger.props.modal.props.editItem = this.props.item;
    this.props.modalTrigger.show();
  }
  render() {
    return (
      <div className="action">
        <span className="action-buttons">
          <Button
            bsStyle="warning"
            bsSize="xsmall"
            onClick={this.edit.bind(this)}>Edit</Button>
          <Button
            bsStyle="danger"
            bsSize="xsmall"
            onClick={this.delete.bind(this)}>Delete</Button>
        </span>
      </div>
    );
  }
}

And finally, we are getting to the DirectorForm component. Working with forms can be tedious, so let's get some help from the formsy-react library, which will help us with managing validations and data model.

import Formsy from 'formsy-react';

export default class DirectorForm extends React.Component {
  constructor(props) {
    super(props);
    // Convert birthday to Date object to allow editing
    if (this.props.editItem) {
      this.props.editItem.birthday = new Date(this.props.editItem.birthday);
    }
    this.refs.directorForm.reset(this.props.editItem);
  }
  submit(model) {
    if (this.props.editItem) {
      DirectorsActions.update(this.props.editItem._id, model);
    }
    else {
      DirectorsActions.add(model);
    }
    this.refs.directorForm.reset();
    // React complains if we update
    // DOM with form validations after close
    // so let's wait one tick
    defer(this.close.bind(this));
  }
  close() {
    this.props.onRequestHide();
  }
  send() {
    this.refs.directorForm.submit();
  }


  render() {
    var title;
    var send;
    var nameError = 'Must have at least 2 letters';
    var textError = 'Must have at least 10 letters';
    var nationError = 'Nationality must be selected';
    if (this.props.editItem) {
      title = 'Edit director ' + this.props.editItem.name;
      send = 'Update';
    }
    else {
      title = 'Add new director';
      send = 'Create';
    }
    return (
      <Modal {...this.props} ref="modalInstance" title={title} animation={false}>
        <div className='modal-body'>
          <Formsy.Form ref="directorForm" onValidSubmit={this.submit.bind(this)}>
            <BootstrapInput
              name="name"
              title="Name"
              type="text"
              validations="minLength:2"
              validationError={nameError}/>
            <SelectInput
              name="nationality"
              title="Nationality"
              options={countries.options}
              validations="minLength:1"
              validationError={nationError}/>
            <PikadayInput
              name="birthday"
              title="Birthday"
              type="text"
              validationError={nameError}/>
            <BootstrapInput
              name="biography"
              title="Biography"
              type="textarea"
              validations="minLength:10"
              validationError={textError}/>
          </Formsy.Form>
        </div>
        <div className='modal-footer'>
          <Button className="pull-left" ref="closeButton" onClick={this.close.bind(this)}>Cancel</Button>
          <Button bsStyle="success" type="submit" onClick={this.send.bind(this)}>{send}</Button>
        </div>
      </Modal>
    );
  }
}