Rebuilding HipChat with React.js

February 10th 2015 Rich Manalang in React, JavaScript, HipChat

The long and the short of it: we rebuilt the HipChat web client from the ground up with React.js, Flux, and a variety of other libraries and it is awesome! Why don't you give it a try?


When HipChat joined Atlassian, it had four clients: web, Adobe Air (Windows, OS X, and Linux), iOS, and an Android app. One of the first goals for the HipChat team was to replace the Air client with a native desktop client for OS X, Windows, and Linux. This kept our small team (at the time) very busy. Because of this focus on delivering first-class app experiences, our web client didn't receive the benefit of updates we were making elsewhere. That sucks, and we're fixing it.

I've long tried to make a case for improving the web client and possibly even rewriting it. It's not an easy sell or decision to rewrite anything, but the HipChat web client sorely needed to be improved all around. There should be few reasons to ask our users to download a native desktop client if we deliver a web client that performs on par or better.

Fast forward

Bob Bergman, Clifton Hensley, and I did the research to come up with a reasonable rewrite plan, what technologies to use, etc. Most product or dev managers are more apt to dismiss a plan to rewrite an app if there's one that works and can be maintained.

Well, we got lucky. The original goal was simple — we needed to apply the Atlassian Design Guidelines (aka, the ADG) to the web client. Applying the ADG to HipChat would unify the look and feel with other Atlassian applications. That goal by itself might have been straightforward with the old web client, though it likely would have been riddled with bugs (since there were zero tests) and would require lots of jQuery soup programming; not a fun task for anyone. Because of the risk of making the existing web client actually worse instead of better, we were granted the runway to rewrite. We looked at a variety of new and popular JS frameworks: Angular, Ember, rolling our own with other smaller libraries... and in the end we looked at React. The rest is history.

The new HipChat web client

Choosing React.js

At first it was difficult to understand the benefits of React since it didn't sell itself well. It was billed as a view library, not a framework. Ember and Angular's popularity a year ago was impossible to ignore. I've built several Angular apps in the past including several Atlassian add-ons like the REST API Browser, Bob built HipChat video using Angular, and Clifton had some experience with Ember.

We all knew about the big benefits you get with using those frameworks (e.g., 2-way data binding, MVC, testing, etc.). All of this made it harder to look at React objectively. We took a few days to prototype out a new HipChat client using each technology. All of them had some benefit, but when we got to React it became perfectly clear why we would want to use it for the next HipChat web client:

  • Component based – This means that we can build reusable components that would allow us to share code with our native clients.
  • Declarative – As with other component architectures, React is declarative. But it's missing the bloat that comes with other component based libraries and is ready to be used today (I'm staring at you Web Components).
  • Virtual DOM – This is probably why most developers are so attracted to React. React manages its own DOM in memory. The most expensive operation most web apps suffer is mutating the DOM. React's approach is to maintain a virtual representation of the DOM which allows it to calculate differences in the DOM so that it only mutates the part of the DOM that actually needs to be updated. This is a huge benefit!
  • Small, it's just a library... not a framework – Having worked with JS frameworks for several years, we all know that frameworks are often bloated and force you to include things you don't need. That maybe ok in the server-side world, but a significant disadvantage with web apps.
  • Simple – As engineers, we all try to follow the KISS principle as much as possible. But often times, the tools we use make it impossible. React is truly simple. The public API can be memorized in a day and once you've built your first component, it's easy to build the next one with confidence that it'll just work.
  • Focuses on unidirectional flow – 2-way data bindings was an awesome idea when it came out. Back in the Backbone days, a lot of us were accustomed to writing lots of boilerplate code to update data throughout our apps. 2-way data bindings simplified all of that. However, it did come with drawbacks – mainly, that you had no idea how your data was getting updated. It was magic. React's approach supports 2-way data binding, but discourages it. Flux, React's application architecture, focuses on a unidirectional data flow and favors data immutability as it flows. The benefit of this is that you know exactly where your data is mutating, making it easier to test and maintain your app.
  • Testability – React components simplify testing greatly. As a proof of it's simplicity, our new web client has more tests than any of our other clients.

In the end, React's biggest benefits are summed up with:

Declarative → Predictable → Confidence *

React's declarative nature allows for predictable behavior that inspires confidence in the apps we build. Pete Hunt, the creator of React, has an excellent introduction in this JSConf video:

* taken from Tom Ochino's React.js Conf keynote

Flux

Flux is Facebook's pattern for React applications that focuses on unidirectional data flow. The basic idea with Flux is that everything happens in one direction. Data flows in as a result of actions. Actions trigger stores (data models) to be updated, which then triggers change events to fire, causing React views to update if needed. The cycle repeats itself as data changes throughout the app.

When Flux was announced, it was just a pattern. Facebook didn't release a library. We adopted the pattern for our new web client. However, because we built our Flux library from scratch, we made some trade offs with how things work. For example, Facebook's Flux abides by the strict notion that the dispatcher has the following traits:

/**
 * Dispatcher is used to broadcast payloads to registered 
 * callbacks. This is different from generic pub-sub systems in 
 * two ways:
 *
 *   1) Callbacks are not subscribed to particular events. 
 *      Every payload is dispatched to every registered callback.
 *   2) Callbacks can be deferred in whole or part until other 
 *      callbacks have been executed.
 **/

However, our dispatcher deviates slightly in that it can be treated as a general pub-sub event emitter. For example, one thing our dispatcher allows us to do is dispatch events during a callback. The problem with this is that our dispatcher allows us to dispatch any event outside of the Flux flow, making it too easy to fall out of the Flux pattern. This is something we hope to fix soon by tightening our dispatcher's responsibilities. With that said, the rest of our new web client pretty much uses the standard Flux components and follows the action to dispatcher to store pattern.

Even with this slight deviation, the new web client's codebase has been very approachable. As a testament to that, we've had code contributions from various parts of Atlassian from developers who haven't used React/Flux before. New developers who join our team have been able to commit and deploy their first feature to production usually by their second day on the job.

Reusable components and hybrid apps

One of our side goals with the new web client is to build out components that can be reused by our other clients — and possibly other Atlassian products. With React, it's possible to build out one component that can work in different clients, web or native. For example, our native desktop clients already use a web view to render the chat panel.

We're currently in the process of applying our React based chat panel to our native desktop clients. This means all of the complex logic of managing message rendering, history fetching, user states (e.g., someone is typing messages, etc.), scrolling animations and state management will all be handled by React — greatly simplifying our native client code.

In the future, we'd love to see React components be shared across our other Atlassian apps. For example, what if we can take HipChat's @mention or emoticon auto complete component and apply it to JIRA, Confluence, Stash, and Bitbucket?

At React.js Conf 2015, Tom Occhino, manager of the React team at Facebook, introduced React Native at React.js Conf. Soon, we'll be able to use React to build truly native components!

We're just getting started with building and using reusable components with HipChat. I can see a future where all of our new apps (web and native) are a composition of new and existing components provided by different Atlassian applications. Lots of possibilities to look forward to!

Where we are today

The new HipChat web client is not entirely complete yet. We have a few features that are still missing, but we're getting closer to feature parity with our other clients day-by-day. Moving forward, we plan to use the new web client as our reference client.

The dev speed we've gained with React+Flux (and its friends: gulp, webpack, lodash, karma) proves that we can release new client features faster and with more confidence on this platform than on any native client.

We've been using the new web client inside Atlassian for several months now and have been slowly releasing to new and existing users. The rest of the world will have access to it soon, but if you're interested in giving it a try, go for it...