Over the holidays I discovered Facebook’s React, an interesting library for generating reactive user interfaces. I wanted to try it out in a real-world application, and I thought of one such application I still had running as a demo: sse-chat, a little chat application I wrote last summer to learn how to make Play Framework and AngularJS cooperate in a very basic way. So I thought, why not rewrite the client side using React, offering the exact same functionality as the AngularJS version. Both are also available in the new version with no changes to the backend code except for the added route, as both versions can be accessed in parallel.
The constraint of making it behave exactly like the AngularJS version was a great practice and it actually only took me an afternoon to complete. Touching the existing demo version also had me notice that the live version of it had been up for like 4 months or so, without any trouble. I have the same experience with my BirdWatch application. Kudos to the Play Framework and Akka developers for enabling such reliable server systems.
Here’s the new version using React instead of AngularJS:
You can open the AngularJS version of the demo inside the article from last summer in another browser and communicate between the two, they share the same backend. Romeo and Juliet are having a chat in room 1, but hey, why not. Better than you just chatting with yourself. You can learn more about the server side in the previous article. In this article we will look exclusively at the web client. Here’s an overview of the architecture with React:
So what is different with the React library? It offers a declarative approach just like AngularJS, but it is subtantially different in many quite interesting ways:
- Components are the basic building blocks, they encapsulate markup and logic together in one place.
- Components receive immutable data (called props) from parent elements.
- Components can have state if necessary.
- React prefers immutable props over mutable state wherever possible, making state changes much easier to reason about.
- Each component knows how to render itself.
- Components can have other components as children. They can pass their own state or props to these as immutable props.
- The entire DOM is rendered into a (fast) virtual DOM with every change made. Changes can either come from mutated state or from parent elements as immutable props.
- This virtual DOM is then diffed against a representation of the current DOM, with the actual DOM only being manipulated where new and old versions differ.
- Data coming from business logic outside will not be touched; React can work with immutable data thoughout.
- Hierarchical components, props, state, handlers. That’s pretty much it, no more rather unintuitive concepts to understand.
How is it different from AngularJS?
What I find most intriguing here is how React can work with immutable data. AngularJS, on the other hand needs to modify data that is used in $scope in order to keep track of changes.
I tried to use AngularJS with ClojureScript a few months back and I ran into a problem with infinite digest loops (StackOverflow), something I quite honestly didn’t want to know about. So the problem seemed to be, and please correct me if I’m wrong, that ClojureScript was handing a shiny new data structure to AngularJS over and over again in order to guarantee immutability internally, just like Underscore generated a new data structure on every call to filter (see the StackOverflow discussion cited above). Angular needs to modify data in order to keep track of updates though, resulting in an infinite cycle that it fortunately is clever enough to stop after a few iterations. Let’s have a quick look at what Angular does with data. It needs to mark individual elements in a collection with a hashKey property in order to keep track of their changes:
1 2 3 4
Note that we also need to change the index.html to have the ng-repeat get the data from a function call, but just follow the link in the code block above to see the full source code for the branch I have created. With these changes in place, every subsequent call to the msgs function will be an array with newly generated objects, causing the following error on every single change to the application state, each of which triggers the digest cycle:
Note that the error output in the browser console is 23KB in size, even when using the minified production version of Angular, so I can only assume this is real problem.
Now in my daytime job I mostly write Scala code and I really like the peace of mind that immutability can give us, so I’d rather not have to depend on letting the UI part of the web application modify the data model just to keep track of changes. I want to further explore immutability in the browser, for example by using ClojureScript or the younger Scala.js, which also allows working with immutable data structures. I have only played around a little bit with the latter, but it certainly is an interesting approach.
A nice example of working with immutable data (from ClojureScript) and React is David Nolen’s great Om library. Immutability allows for amazing features like a simple undo functionality, even saving the entire history of state mutation during the lifecycle of the application. It shouldn’t be too difficult to achieve the same in Scala.js, for example by modelling the application state as an immutable data structure and then pushing each version into an array that is then used as a stack. Pop the last state and render what you can peek at, undo done. Conceptually this is really simple to think about iff (if and only if) your UI rendering code requires no state of its own and simply renders an immutable data structure, which React is capable of.
Source code time
The HTML for our app becomes very simple. In this application it is called react.scala.html, but that’s really only because it made the hookup to a route easier, otherwise there is no good reason to use a play/scala template here:
1 2 3 4 5 6 7 8 9
All we do above is provide a DOM element hosting the application markup plus loading the necessary scripts. All the interesting stuff happens inside react.app.js, which is loaded last. Let us go through, component by component, starting from the top of the hierarchy with the ChatApp component:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
- Inside the getInitialState function, the initial state is provided.
- The listen function initiates the SSE connection for the current chat room, potentially closing an already established connection. It is organized as an enclosing function that is immediately called upon initialization of the component. That initial call sets up a chatFeed var and returns another function that henceforth lives inside the listen property of the component. This function can then be called when an open connection to the SSE stream for the current room is desired.
- The addMsg function mutates component state by calling this.setState.
- The handleRoomChange and handleNameChange functions modify room and name state. handleRoomChange also calls listen again to re-establish the SSE stream for the new room.
- The componentWillMount function establishes the SSE connection by calling listen(room), once, upon initialization of the component.
Next let’s look at the first child component of the single ChatApp component.
1 2 3 4 5 6 7 8 9 10 11 12 13
In the NameRoomBox component, only two things actually happen:
- The roomOpts property is initialized with a list of all 5 room
- The render function returns a
<input>for the name and a
<select>for the room inside, using the
<option>elements created in the first step. It also attaches the handler functions provided inside props to respond to user input.
The next component inside ChatApp is the MsgList component:
1 2 3 4 5 6 7 8 9
The MsgList component only has one function: render, which takes the array of messages provided as props and maps it into individual ChatMsg components, which we will look at next:
1 2 3 4 5 6 7 8 9 10
The ChatMsg component above only knows how to render itself. Depending on the name of the user sending a message, it is rendered in different colors by assigning the element different CSS classes. Now the last component to look at is the SaySomethingBox:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
In the SaySomethingBox component, two things happen:
+ The render function renders the UI and attaches the handleSubmit function to the events fired by either submitting the form by pressing enter inside the
<input> element or clicking the submit button.
+ The handleSubmit function POSTs the text in the
<input> field to the server using jQuery’s
$.ajax function. It uses the name and room from props to construct the JSON message. The message POSTing logic could just as well live inside the top level component, maybe it should, but I don’t feel like changing it right now.
That’s pretty much it, with one last function call to get the whole application started:
So far all this may sound like a lot of praise for React, but let me emphasize where AngularJS really has the edge at this point:
- Workable best practices for organizing large applications.
- Many more online resources. I don’t feel much love for Angular’s own documentation, but at least there are plenty of great tutorials and blog articles about it out there.
- Reasonably mature support for automated building and testing.
So, will I continue using AngularJS? Yes. Am I curious about doing more with React? Another yes. I need a larger project in order to experience React in a more complex setting. Oh, there’s one project that comes to mind, but that’s a story for another day. Before I forget: you can find the source code for the application on GitHub.