marty.js v0.9 by James Hollingworth

It's been 2 months since we first publicly announced Marty. Since then we've been working hard building new features and refining our API. Today I'm happy to announce Marty v0.9!

So what's new in this release?

Isomorphisim

Being able to run your JS application on the server and in the browser has been one of the major reasons people have started using React. We've found it's still challenging to build isomorphic applications and so we've built a set of APIs that make it easier.

Marty.renderToString is a smarter version of React.renderToString which knows what state each component needs, fetches it for you and then renders the component. It also gives you lots of diagnostic information to understand what Marty is doing on the server:

Marty Developer Tools

For synchronizing state between the server and browser we have Marty.dehydrate() and Marty.rehydrate(). Marty.dehydrate() serializes the contents of your stores to a string which is sent down with the rendered HTML. When your application first loads in the browser you just need to call Marty.rehydrate() to have your stores return back to their state when on the server.

We've also created marty-express, a middleware for express.js which consumes react-router routes so you don't have to have to duplicate your routing on the client and the server. It also modifies the HTTP state source so HTTP requests made on the server will behave the same as they would in the browser.

If you'd like to learn more, we've written a guide on how to get started. Our example chat app has also been updated to be isomorphic.

ES6 Classes

To complement React ability to define components using ES6 classes we now support defining all types using classes as well. You can read our guide on ES6 to find out how to make the switch. We have also created a new version of the example chat app which uses ES6 classes.

var UsersStore = Marty.createStore({
  id: 'UsersStore',
  handlers: {
    addUser: Constants.RECEIVE_USER
  },
  getInitialState: function () {
    return [];
  },
  addUser: function (user) {
    this.state.push(user);
    this.hasChanged();
  }
});
class UsersStore extends Marty.Store {
  constructor(options) {
    super(options);
    this.state = [];
    this.handlers = {
      addUser: Constants.RECEIVE_USER
    };
  }
  addUser(user) {
    this.state.push(user);
    this.hasChanged();
  }
}

Containers

When defining components using ES6 classes you can no longer use mixins which means you won't be able to use the state mixin. A new pattern emerging to solve this problem is to wrap your components in "container" components. We're a big fan of this pattern and so we've introduced Marty.createContainer which creates a container component that does the same job as the state mixin. You can checkout our containers guide to learn more about them.

class User extends React.Component {
  render() {
    return <div className="User">{this.props.user}</div>;
  }
}

module.exports = Marty.createContainer(User, {
  listenTo: UserStore,
  fetch: {
    user() {
      return UserStore.for(this).getUser(this.props.id);
    }
  },
  failed(errors) {
    return <div className="User User-failedToLoad">{errors}</div>;
  },
  pending() {
    return this.done({
      user: {}
    });
  }
});

Queries

Queries are a new type we're introducing that is responsible for coordinating getting new state from outside of the application. They should help reduce some confusion about how to dispatch actions when fetching state. You can read our guide to learn why they've been introduced.

var UserQueries = Marty.createQueries({
  id: 'UserQueries',
  getUser: function (id) {
    this.dispatch(UserActions.RECEIVE_USER_STARTING, id);
    UserAPI.getUser(id).then(function (res) {
      if (res.status === 200) {
        this.dispatch(UserActions.RECEIVE_USER, res.body, id);
      } else {
        this.dispatch(UserActions.RECEIVE_USER_FAILED, id);
      }
    }.bind(this)).catch(function (err) {
      this.dispatch(UserActions.RECEIVE_USER_FAILED, id, err);
    }.bind(this))
  }
});
class UserQueries extends Marty.Queries {
  getUser(id) {
    this.dispatch(UserActions.RECEIVE_USER_STARTING, id);
    UserAPI.getUser(id).then((res) => {
      if (res.status === 200) {
        this.dispatch(UserActions.RECEIVE_USER, res.body, id);
      } else {
        this.dispatch(UserActions.RECEIVE_USER_FAILED, id);
      }
    }).catch((err) => this.dispatch(UserActions.RECEIVE_USER_FAILED, id, err));
  }
}

Action creators

Our approach to defining action creators was the source of lots of confusion and many bugs. We've got a new, simplified approach which should resolve these issues.

var UserConstants = Marty.createConstants(["UPDATE_EMAIL"]);

var UserActionCreators = Marty.createActionCreators({
  id: 'UserActionCreators',
  updateEmail: function (userId, email) {
    this.dispatch(UserConstants.UPDATE_EMAIL, userId, email)
  }
});
var UserConstants = Marty.createConstants(["UPDATE_EMAIL"]);

class UserActionCreators extends Marty.ActionCreators {
  updateEmail(userId, email) {
    this.dispatch(UserConstants.UPDATE_EMAIL, userId, email)
  }
}

Components only re-rendered once per action

There was an interesting discussion in the Flux panel at React Conf about batching store updates to improve performance. Marty now does this automatically for you so no matter how many times your store changes your components will only be re-rendered once per action.

Developer Tools

Marty Developer Tools has been given an overhaul. It can now tell you things like what components re-rendered as a result of the action and which stores caused a component to re-render. It also gives you the ability to reset the applications state to a specific action (think get reset --hard actionId).

Marty Developer Tools

lodash

Thanks to @jdalton we've moved from underscore.js to lodash (This is an internal change so wont effect you if you're using underscore in your application). This has resulted in a 6KB drop in our gzipped file size. Unfortunately Marty has also grown by 6KB so Marty v0.9 is the same size as Marty v0.8 :(

Changelog

For those currently using Marty v0.8, we've written a guide for upgrading to v0.9. @dariocravero also has an excellent guide on making the move to ES6.

Breaking changes

Store#setState has been renamed to Store#replaceState. Store#setState will now merge the existing state with the new state. This is to more closely follow the React API.

New features

  • Isomorphism (#13)
  • CookieStateSource & LocationStateSource (#205)
  • ES6 Classes (#89)
  • Add dataType option to http state source (#176)
  • Lodash v3 instead of underscore (#170)
  • HttpStateSource hooks (#118)
  • FetchResult#toPromise (#180)
  • Clear fetch history in Store#clear (#149)
  • Batch store change events (#183)
  • Allow you to specify when function context (#184)
  • Marty.createContainer (#206)
  • Set request credentials to 'same-origin' (#209)

Bugs

  • dependsOn doesn't update when dependent store updates (#113)
  • Don't auto set content-type if using FormData (#140)
  • Fetch API compatibility (#133)

Deprecations