Productive Rage

Dan's techie ramblings

TypeScript / ES6 classes for React components - without the hacks!

React 0.13 has just been released into beta, a release I've been eagerly anticipating! This has been the release where finally they will be supporting ES6 classes to create React components. Fully supported, no messing about and jumping through hoops and hoping that breaking API changes don't drop in and catch you off guard.

Back in September, I wrote about Writing React components in TypeScript and realised that before I had actually posted it that the version of React I was using was out of date and I would have to re-work it all again or wait until ES6 classes were natively supported (which was on the horizon back then, it's just that there were no firm dates). I took the lazy option and have been sticking to React 0.10.. until now!

Update (16th March 2015): React 0.13 was officially released last week, it's no longer in beta - this is excellent news! There appear to be very little changed since the beta so everything here is still applicable.

Getting the new code

I've got my head around npm, which is the recommended way to get the source. I had a few teething problems a few months ago with first getting going (I need python?? Oh, not that version..) but now everything's rosy. So off I went:

npm install react@0.13.0-beta.1

I saw that the "lib" folder had the source code for the files, the dependencies are all nicely broken up. Then I had a small meltdown and stressed about how to build from source - did I need to run browserify or something?? I got that working, with some light hacking it about, and got to playing around with it. It was only later that I realised that there's also a "dist" folder with built versions - both production (ie. minified) and development. Silly boy.

To start with, I stuck to vanilla JavaScript to play around with it (I didn't want to start getting confused as to whether any problems were with React or with TypeScript with React). The online JSX Compiler can perform ES6 translations as well as JSX, which meant that I could take the example

class HelloMessage extends React.Component {
  render() {
    return <div>Hello {this.props.name}</div>;
  }
}

React.render(<HelloMessage name="Sebastian" />, mountNode);

and translate it into JavaScript (this deals with creating a class, derives it "from React.Component" and it illustrates what the JSX syntax hides - particularly the "React.createElement" call):

var ____Class1 = React.Component;
for (var ____Class1____Key in ____Class1) {
  if (____Class1.hasOwnProperty(____Class1____Key)) {
    HelloMessage[____Class1____Key] = ____Class1[____Class1____Key];
  }
}
var ____SuperProtoOf____Class1 = ____Class1 === null ? null : ____Class1.prototype;
HelloMessage.prototype = Object.create(____SuperProtoOf____Class1);
HelloMessage.prototype.constructor = HelloMessage;
HelloMessage.__superConstructor__ = ____Class1;

function HelloMessage() {
  "use strict";
  if (____Class1 !== null) {
    ____Class1.apply(this, arguments);
  }
}
HelloMessage.prototype.render = function() {
  "use strict";
  return React.createElement("div", null, "Hello ", this.props.name);
};

React.render(
  React.createElement(HelloMessage, { name: "Sebastian" }),
  mountNode
);

I put this into a test page and it worked! ("mountNode" just needs to be a container element - any div that you want to render your content inside).

The derive-class code isn't identical to that you see in TypeScript's output. If you've looked at what TypeScript emits, this might be familiar:

var __extends = this.__extends || function (d, b) {
  for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
  function __() { this.constructor = d; }
  __.prototype = b.prototype;
  d.prototype = new __();
};

I tried hacking this in, in place of the inheritance approach from the JSX Compiler and it still worked. I presumed it would, but it's always best to take baby steps if you don't understand it all perfectly - and I must admit that I've been a bit hazy on some of React's terminology around components, classes, elements, factories, whatever.. (despite having read "Introducing React Elements" what feels like a hundred times).

Another wrong turn

In the code above, the arrangement of the line

React.render(
  React.createElement(HelloMessage, { name: "Sebastian" }),
  mountNode
);

is very important. I must have spent hours earlier struggling with getting it working in TypeScript because I thought it was

React.render(
  React.createElement(new HelloMessage({ name: "Sebastian" })),
  mountNode
);

It's not.

It it not a new instance passed to "createElement"; it's a type and a properties object. I'm not sure where I got the idea from that it was the other way around - perhaps because I got all excited about it working with classes and then presumed that you worked directly with instances of those classes. Doh.

Time for TypeScript!

Like I said, I've been clinging to my hacked-about way to get TypeScript working with React until now (waiting until I could throw it away entirely, rather than replace it for something else.. which I would then have to throw away entirely when this release turned up). I took a lot of inspiration from code in the react-typescript repo. But that repo hasn't been kept up to date (for the same reason as I had, I believe, that the author knew that it was only going to be required until ES6 classes were supported). There is a link there to typed-react, which seems to have been maintained for 0.12. This seemed like the best place to start.

Update (16th March 2015): With React 0.13's official release, the DefinitelyTyped repo has been updated and now does work with 0.13, I'm leaving the below section untouched for posterity but you might want to skip to the next section "Writing a TypeScript React component" if you're using the DefinitelyTyped definition.

In fact, after some investigation, very little needs changing. Starting with their React type definitions (from the file typings/react/react.d.ts), we need to expose the "React.Component" class but currently that is described by an interface. So the following must be changed -

interface Component<P> {
  getDOMNode<TElement extends Element>(): TElement;
  getDOMNode(): Element;
  isMounted(): boolean;
  props: P;
  setProps(nextProps: P, callback?: () => void): void;
  replaceProps(nextProps: P, callback?: () => void): void;
}

for this -

export class Component<P> {
  constructor(props: P);
  protected props: P;
}

I've removed isMounted and setProps because they've been deprecated from React. I've also removed the getDOMNode methods since I think they spill out more information than is necessary and I've removed replaceProps since the way that I've been using React I've not seen the use for it - I think it makes more sense to request a full re-render* rather than poke things around. You may not agree with me on these, so feel free to leave them in! Similarly, I've changed the access level of "props" to protected, since I don't think that it should be public information. This requires TypeScript 1.3, which might be why the typed-react version doesn't specify it.

* When I say "re-render", I mean that when some action changes the state of the application, I call React.render again and let the Virtual DOM do it's magic around making this efficient. Plus I'm experimenting at the moment with making the most of immutable data structures and returning false from shouldComponentUpdate where it's clear that the data can't have changed - so the Virtual DOM has less work to do. But that's straying from the point of this post a bit..

Then the external interface needs changing from

interface Exports extends TopLevelAPI {
  DOM: ReactDOM;
  PropTypes: ReactPropTypes;
  Children: ReactChildren;
}

to

interface Exports extends TopLevelAPI {
  DOM: ReactDOM;
  PropTypes: ReactPropTypes;
  Children: ReactChildren;
  Component: Component<any>;
}

Quite frankly, I'm not 100% sure why specifying "Component" works as it does, since I would have thought that you could only then inherit from "Component", rather than being able to specify whatever type param that you want. But it does work, thankfully (my understanding of type definitions is a little shallow at this point, so there's very likely something here that I don't quite understand which allows it work as it does).

Writing a TypeScript React component

So now we can write this:

import React = require('react');

interface Props { name: string; role: string; }

class PersonDetailsComponent extends React.Component<Props> {
  constructor(props: Props) {
    super(props);
  }
  public render() {
    return React.DOM.div(null, this.props.name + " is a " + this.props.role);

  }
}

function Factory(props: Props) {
  return React.createElement(PersonDetailsComponent, props);
}

export = Factory;

Note that we are able to specify a type param for "React.Component" and, when you edit this in TypeScript, "this.props" is correctly identified as being of that type.

Update (16th March 2015): If you are using the DefinitelyTyped definitions then you need to specify both "Props" and "State" type params (I recommend that Component State never be used and that it always be specified as "{}", but that's out of the scope of this post) - ie.

class PersonDetailsComponent extends React.Component<Props, {}> {

The pattern I've used is to declare a class that is not exported. Rather, a "Factory" function is made available to the world. This is to prevent the problem that I described earlier - originally I was exporting the class and was trying to call

React.render(
  React.createElement(new PersonDetailsComponent({
    name: "Bob",
    role: "Mouse catcher"
  })),
  mountNode
);

but this does not work. The correct approach is to export a Factory method and then to consume the component thusly:

React.render(
  PersonDetailsComponent({
    name: "Bob",
    role: "Mouse catcher"
  }),
  this._renderContainer
);

Thankfully, the render method is specified in the type definition as

render<P>(
  element: ReactElement<P>,
  container: Element,
  callback?: () => void
): Component<P>;

so, if you forget to apply the structure of non-exported-class / exported-Factory-method and tried to export the class and new-one-up and pass it to "React.render" directly, you would get a compile error such as

Argument of type 'PersonDetailsComponent' is not assignable to parameter of type 'ReactElement<Props>'

I do love it when the compiler can pick up on your silly mistakes!

Update (16th March 2015): Again, there is a slight difference between the typed-react definition that I was originally using and the now-updated DefinitelyTyped repo version. With DefinitelyTyped, the render method is specified as:

render<P, S>(
    element: ReactElement<P>,
    container: Element,
    callback?: () => any
): Component<P, S>

but the meaning is much the same.

Migration plan

The hacky way I've been working until now did allow instances of component classes to be used, so migrating over is going to require some boring mechanical work to change them - and to add Factory methods to each component. But, since they all shared a common base class (the "ReactComponentBridge"), it also shouldn't be too much work to change those base classes to "React.Component" in one search-and-replace. And there aren't too many other breaking changes to worry about. I was using "setProps" earlier on in development but I've already gotten rid of all those - so I'm optimistic that moving over to 0.13 isn't going to be too big of a deal.

It's worth bearing in mind that 0.13 is still in beta at the moment, but it seems like the changes that I'm interested in here are unlikely to vary too much between now and the official release. So if I get cracking, maybe I can finish migrating not long after it's officially here - instead of being stuck a few releases behind!

Posted at 01:02

Comments

TypeScript classes for (React) Flux actions

I've been playing with React over the last few months and I'm still a fan. I've followed Facebook's advice and gone with the "Flux" architecture (there's so many good articles about this out there that I couldn't even decide which one to link to) but I've been writing the code using TypeScript. So far, most of my qualms with this approach have been with TypeScript rather than React; I don't like the closing-brace formatting that Visual Studio does and doesn't let you change, its generics system is really good but not quite as good as I'd like (not as good as C#'s, for example, and I sometimes wish generic type params were available at runtime for testing but I do understand why they're not). I wish the "Allow implicit 'any' types" option defaulted to unchecked rather than checked (I presume this is to encourage "gradual typing" but if I'm using TypeScript I'd rather go whole-hog).

But what I thought were going to be the big problems with it haven't been, really - type definitions and writing the components (though I am using a bit of a hack that relies upon an older version of React - I'm hoping to change this when 0.13 comes out and introduces better support for ES6 classes).

Writing the components in "pure" TypeScript results in more code than jsx.. it's not the end of the world, but something that would combine the benefits of both (strong typing and succint jsx format) would be wonderful. There are various possibilities that I believe people are looking into, from modifying the TypeScript compiler to support jsx to the work that Facebook themselves are doing around "Flow" which "Adds static typing to JavaScript to improve developer productivity and code quality". Neither of these are ready for me to integrate into Visual Studio, which I'm still using since I like it so much for my other development work.

What I want to talk about today, though, is one of the ways that TypeScript's capabilities can make a nice tweak to how the Flux architecture may be realised. Hopefully the following isn't blindly obvious and well-known, I failed to find any other posts out there explaining it so I'm going to try to take credit for it! :)

Here's the diagram that everyone who's looked into Flux will have seen many times before (since I've nicked it straight from the React blog's post about it) -

The Flux Architecture

In the middle are the "Action Creators", which create objects that represent actions (and any associated data) so that the Dispatcher has something to send out. Stores listen for these actions - checking whether a given action is one that they're interested in and extracting the information from it as required.

As a concrete example, here is how actions are created in Facebook's "TODO" example (from their repo on GitHub):

/*
 * Copyright (c) 2014, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * TodoActions
 */

var AppDispatcher = require('../dispatcher/AppDispatcher');
var TodoConstants = require('../constants/TodoConstants');

var TodoActions = {

  /**
   * @param  {string} text
   */
  create: function(text) {
    AppDispatcher.dispatch({
      actionType: TodoConstants.TODO_CREATE,
      text: text
    });
  },

  /**
   * @param  {string} id The ID of the ToDo item
   * @param  {string} text
   */
  updateText: function(id, text) {
    AppDispatcher.dispatch({
      actionType: TodoConstants.TODO_UPDATE_TEXT,
      id: id,
      text: text
    });
  },

  /**
   * Toggle whether a single ToDo is complete
   * @param  {object} todo
   */
  toggleComplete: function(todo) {
    var id = todo.id;
    if (todo.complete) {
      AppDispatcher.dispatch({
        actionType: TodoConstants.TODO_UNDO_COMPLETE,
        id: id
      });
    } else {
      AppDispatcher.dispatch({
        actionType: TodoConstants.TODO_COMPLETE,
        id: id
      });
    }
  },

  /**
   * Mark all ToDos as complete
   */
  toggleCompleteAll: function() {
    AppDispatcher.dispatch({
      actionType: TodoConstants.TODO_TOGGLE_COMPLETE_ALL
    });
  },

  /**
   * @param  {string} id
   */
  destroy: function(id) {
    AppDispatcher.dispatch({
      actionType: TodoConstants.TODO_DESTROY,
      id: id
    });
  },

  /**
   * Delete all the completed ToDos
   */
  destroyCompleted: function() {
    AppDispatcher.dispatch({
      actionType: TodoConstants.TODO_DESTROY_COMPLETED
    });
  }

};

module.exports = TodoActions;

Every action has an "actionType" property. Some have an "id" property, some have a "text" property, some have both, some have neither. Other examples I've seen follow a similar pattern where the ActionCreator (or ActionCreators, since sometimes there are multiple - as in the chat example in that same Facebook repo) is what is responsible for knowing how data is represented by each action. Stores assume that if the "actionType" is what they expect then all of the other properties they expect to be associated with that action will be present.

Here's a snippet I've taken from another post:

var action = payload.action;
switch(action.actionType){
  case AppConstants.ADD_ITEM:
    _addItem(payload.action.item);
    break;
  case AppConstants.REMOVE_ITEM:
    _removeItem(payload.action.index);
    break;
  case AppConstants.INCREASE_ITEM:
    _increaseItem(payload.action.index);
    break;
  case AppConstants.DECREASE_ITEM:
    _decreaseItem(payload.action.index);
    break;
}

Some actions have an "item" property, some have an "index". The ActionCreator was responsible for correctly populating data appropriate to the "actionType".

Types, types, types

When I first start writing code like this for my own projects, it felt wrong. Wasn't I using TypeScript so that I had a nice reassuring type safety net to protect me against my own mistakes?

Side note: For me, this is one of the best advantages of "strong typing", the fact the compiler can tell me if I've mistyped a property or argument, or if I want to change the name of one of them then the compiler can change all references rather than it being a manual process. The other biggie for me is how beneficial it can be in helping document APIs (both internal and external) - for other people using my code.. or just me when it's been long enough that I can't remember all of the ins and outs of what I've written! These are more important to me than getting worried about whether "static languages" can definitely perform better than "dynamic" ones (let's not open that can of worms).

Surely, I asked myself, if these objects have properties that vary based upon an "actionType" magic string, these would be better expressed as actual types? Like classes?

Working from the example above, there would be classes such as:

class AddItemAction {
  constructor(private _index: number) { }
  get index() {
    return this._index;
  }
}
export = AddItemAction;

I'm a fan of the AMD pattern so I would have a separate file per action class and then explicitly "import" (in TypeScript terms) them into Stores that reference them. The main reason I'm leaning towards the AMD pattern is that you can use require.js to load in the script required to render the first "page" and then dynamically load in additional script as more functionality of the application is used. This should avoid the risk of the dreaded multi-megabyte initial download (and the associated delays). I'm still proving this to myself - it's looking very promising so far but I haven't written any multi-megabyte applications yet!

I also like things to be immutable, otherwise the above could have been shortened even further to:

class AddItemAction {
  constructor(public index: number) { }
}
export = AddItemAction;

But, technically, this could lead to one Store changing data in an action, which could affect what another Store does with the data. An effect that would only happen if that first Store received the action before the second one. Yuck. I don't imagine anyone would want to do something like that but immutability means that it's not even possible, even by accident (especially by accident).

So if there were classes for each action then the listening code would look more like this:

if (action instanceof AddItemAction) {
  this._addItem(action);
}
if (action instanceof RemoveItemAction) {
  this._removeItem(action);
}
if (action instanceof IncreaseItemAction) {
  this._increaseItem(action);
}
if (action instanceof DecreaseItemAction) {
  this._decreaseItem(action);
}

I prefer to have the functions receive the actual action. The AddItemAction instance is passed to the "_addItem" function, for example, rather than just the "index" property value - eg.

private _addItem(action: AddItemAction) {
  // Do whatever..
}

This is at least partly because it makes the type comparing code more succinct - the "action" reference will be of type "any" (as will be seen further on in this post) and so TypeScript lets us pass it straight in to methods such as _addItem since it presumes that if it's "any" then it can be used anywhere, even as an function argument that has a specific type annotation. The type check that is made before _addItem is called gives us the confidence that the data is appropriate to pass to _addItem, the TypeScript compiler will then happily take our word for it.

Update (25th February 2015): A couple of people in the comments suggested that the action property on the payload should implement an interface to "mark" it as an action. This is something I considered originally but I dismissed it and I think I'm going to continue to dismiss it for the following reason: the interface would be "empty" since there is no property or method that all actions would need to share. If this were C# then every action class would have to explicitly implement this "empty interface" and so we could do things like search for all implementation of IAction within a given project or binary. In TypeScript, however, interfaces may be implemented implicitly ("TypeScript is structural"). This means that any object may be considered to have (implicitly) implemented IAction, if IAction is an empty interface. And this means that there would be no reliable way to search for implementations of IAction in a code base. You could search for classes that explicitly implement it, but if you have to rely upon people to follow the convention of decorating all action classes with a particular interface then you might as well rely on a simpler convention such as keeping all actions within files under an "action" folder.

Server vs User actions

Another concept that this works well with is one that I think I first read at Atlassian's blog: Flux Step By Step - the idea of identifying a given action as originating from a view (from a user interaction, generally) or from the server (such as an ajax callback).

They suggested the use of an AppDispatcher with two distinct methods, each wrapping an action up with an appropriate "source" value -

var AppDispatcher = copyProperties(new Dispatcher(), {

  /**
   * @param {object} action The details of the action, including the action's
   * type and additional data coming from the server.
   */
  handleServerAction: function(action) {
    var payload = {
      source: 'SERVER_ACTION',
      action: action
    };
    this.dispatch(payload);
  },

  /**
   * @param {object} action The details of the action, including the action's
   * type and additional data coming from the view.
   */
  handleViewAction: function(action) {
    var payload = {
      source: 'VIEW_ACTION',
      action: action
    };
    this.dispatch(payload);
  }

});

Again, these are "magic string" values. I like the idea, but TypeScript has the tools to do better.

I have a module with an enum for this:

enum PayloadSources {
  Server,
  View
}
export = PayloadSources;

and then an AppDispatcher of my own -

import Dispatcher = require('third_party/Dispatcher/Dispatcher');
import PayloadSources = require('constants/PayloadSources');
import IDispatcherMessage = require('dispatcher/IDispatcherMessage');

var appDispatcher = (function () {
  var _dispatcher = new Dispatcher();
  return {
    handleServerAction: function (action: any): void {
      _dispatcher.dispatch({
        source: PayloadSources.Server,
        action: action
      });
    },

    handleViewAction: function (action: any): void {
      _dispatcher.dispatch({
        source: PayloadSources.View,
        action: action
      });
    },

    register: function (callback: (message: IDispatcherMessage) => void): string {
      return _dispatcher.register(callback);
    },

    unregister: function (id: string): void {
      return _dispatcher.unregister(id);
    },

    waitFor: function (ids: string[]): void {
      _dispatcher.waitFor(ids);
    }
  };
} ());

// This is effectively a singleton reference, as seems to be the standard pattern for Flux
export = appDispatcher;

The IDispatcherMessage is very simple:

import PayloadSources = require('constants/PayloadSources');
interface IDispatcherMessage {
  source: PayloadSources;
  action: any
}
export = IDispatcherMessage;

This allows me to listen for actions with code thusly -

AppDispatcher.register(message => {
  var action = message.action;
  if (action instanceof AddItemAction) {
    this._addItem(action);
  }
  if (action instanceof RemoveItemAction) {
    this._removeItem(action);
  }
  // etc..

Now, if I come across a good reason to rename the "index" property on the AddItemAction class, I can perform a refactor action that will fix it everywhere. If I don't use the IDE to perform the refactor, and just change the property name in one place, then I'll get TypeScript compiler errors about an "index" property that no longer exists.

The mysterious Dispatcher

One thing I skimmed over in the above is what the "third_party/Dispatcher/Dispatcher" component is. The simple answer is that I took the Dispatcher.js file from the Flux repo and messed about with it a tiny bit to get it to compile as TypeScript with my preferred disabling of the option "Allow implicit 'any' types". In case this is a helpful place for anyone to start, I've put the result up on pastebin as TypeScript Flux Dispatcher, along with the required support class TypeScript Flux Dispatcher - invariant support class.

Final notes

I'm still experimenting with React and Flux but this is one of the areas that I've definitely been happy with. I like the Flux architecture and the very clear way in which interactions are handled (and the clear direction of flow of information). Describing the actions with TypeScript classes feels very natural to me. It might be that I start grouping multiple actions into a single module as my applications get bigger, but for now I'm fine with one per file.

The only thing I'm only mostly happy with is my bold declaration in the AppDispatcher class; "This is effectively a singleton reference, as seems to be the standard pattern for Flux". It's not the class that's exported from that module, it's an instance of the AppDispatcher which is used by everything in the app. This makes sense in a lot of ways, since it needs to be used in so many places; there will be various Stores that register to listen to it but there are likely to be many, many React components, any one of which could accept some sort of interaction that requires an action be created (and so be sent to the AppDispatcher). One alternative approach would be to use dependency injection to pass an AppDispatcher through every component that might need it. In fact, I did try that in one early experiment but found it extremely cumbersome, so I'm happy to settle for what I've got here.

However, the reason (one of, at least!) that singletons got such a bad name is that they can making unit testing very awkward. I'm still in the early phases of investigating what I think is the best way to test a React / Flux application (there are a lot of articles out there explaining good ways to tackle it and I'm trying to work my way through some of their ideas). One thing that I'm contemplating, particularly for testing simple React components, is to take advantage of the fact that I'm using AMD everywhere and to try changing the require.js configuration for tests - for any given test, when an AppDispatcher is requested, some sort of mock object could be provided in its place.

This would have the two main benefits that it could expose convenient methods to confirm that a particular action was raised following a given interaction (which may be the main point of that particular test) but also that there would be no shared state that needs resetting between tests; each test would provide its own AppDispatcher stand-in. I've not properly explored this yet, it's still in the idea phase, but I think it also has promise. And - if it all goes to plan - it's another reason way for me to convince myself that AMD loading within TypeScript is the way to go!

Posted at 22:24

Comments