Rx-ifying a chat room built with ReactJS and Socket.io
My 2016 goal is to rx-ify everything. My second goal is to do more of blog post driven development (shoutout to @ashfurrow). I’ll share in this post how to build a chat room using RxJS, ReactJS and SocketIO. Cherry on the cake is MaterializeCSS because I like material design. The gif below shows what we are going to build.
Better yet, you are invited to experience the live demo on your own.
Or go straight to the source.
This chat room is different than many other demos because as you can see, it handles not only the real time chat messages, but also the connect and disconnect events. When a user joins the chat room, the list of active users is updated automatically and immediately. Likewise, when a user exits, i.e. closes the browser, other remaining users also see the list getting refreshed. To highlight this feature but keeping it simple, I code so that when a user joins, he/she will get a random nickname instead of having to deal with any authentication.
RxJS and SocketIO work really well with ReactJS because they still allow for unidirection data flow but in my opinion keeps things much simpler than the concepts in Flux. Yes, I knowthis is philosophical and debatable.
Anyhow, obviously we write the client side using the React library. The entry point to the program is the class Main and it looks as follow:
I pulled the function createRandomNickname from this answer on Stackoverflow. This nickname is passed to Main as a props. Nothing interesting yet. Let’s define Main next:
From the function getInitialState and according to our intuition, the state of Main is an object that contains a list of users and a list of messages. Initially, both lists are empty. Main consists of 3 smaller building blocks, a component at the top called AppBar, a ChatPane on the left and a PresencePane on the right. Again, that’s what shown in the render function which is consistent with the screen capture above. The list of messages and the list of users that make up the state of Main (as mentioned above) are passed to the ChatPane and PresencePane as props, respectively.
Much more interesting is what we do in the componentDidMount function of Main.
In here, obviously, we must first create the socket by calling the function io in the SocketIO library. We define 3 events that come from the server: ‘my socketid’, ‘all users’, and ‘message’. For each of these 3 events, we create a corresponding Rx.Observable. Using the socket above, we “listen” to the three events. When these event callbacks of the socket are called, the observable will be calling the onNext function on its observer. These are hot observables and they keep happening in the future, so there is no onComplete. SocketIO also does not have callback for errors, so there is no onError to be called either.
The first subscriber subscribes to the ‘my socketid’ observable event. It takes the socketId and connectTime received along with the event, turns around and sends back to the server with the nickname. This data flow from client to server is via the socket event ‘client connect’. The reason for doing so is the server needs to correlate the client’s socketId and the nickname. It is worth mentioning the these event names are totally up to us to choose. I just happen to call them likewhat I did. Before continuing on with the second subscriber, let’s understand the server and client interaction in the sequence diagram below. In this diagram, let’s assume that Client1 has already been connected and Client2 starts to connect to the server. In this diagram, time increases as we down vertically. At some point later, Client1 will disconnect and thanks to SocketIO, the server will detect this (represented by second arrow from the bottom that goes from Client1 to Server). The arrows from the Server to Client1 and Client2 are the socket events (‘my socketid’, ‘all users’, and ‘message’) that we mentioned earlier. I just mentioned about the first kind of data flow from clients to server which is the socket event ‘client socket’. The other data flow from clients to server is the HTTP POST.
The second subscriber subscribes to the ‘all users’ observable event. The data in this event is the list of active users. Recall the state of Main (that we discussed earlier) consists of two lists, one of which is the list of users. How convenient, now all we do is simply calling setState in Main and pass the list of users.
The third subscriber subscribes the the ‘message’ observable event. It receives the list of messages and again passing that list when it calls setState of Main.
As we know with ReactJS, when setState is called, it causes the component Main to be re-rendered, which in turn causing the children components of Main, namely the ChatPane and the PresencePane to be re-rendered.
The AppBar component is very simple. It only has a render function, and it doesn’t even depend on its state or props whatsoever to do the rendering.
The PresencePane component is also simple as it also only contains a render function. The only difference is that it renders using its props which is nothing but a list of users that Main passes to it.
The ChatPane component works similarly like the PresencePane except it deals with list of messages instead of list of users. But the ChatPane also contains an input text field and a button. Users type on the text field and when they press the Enter key or click on the Send Floating Action Button, it sends a text message to the server via SocketIO. RxJS can be applied here since key press and button clicks are also events. You can read more about this in my previous post here. Instead of just writing to the console as in the previous post, in the onNext function here, the subscriber sends the HTTP POST request to the server. The body of this POST request carries the message.
I implement the server as an Express application. We can also use RxJS on the server just as we do with the client. We create 2 observables, one for connect and one for disconnect event.
The first observable and observer are implemented just like in the client:
When a browser client connects to the server, SocketIO calls the callback function which retrieves the client socket id and sends back to the client via the ‘my socketId’ (remember from earlier). The server SocketIO then listens to the ‘client connect’ message from the client. When that happens, the observable will call the onNext function of the observer which in turn broadcast the ‘all users’ to all clients.
The disconnect works exactly the same from RxJS perspective, i.e create an observable, have an observer subscribing to it.
If you’re with me so far, you would recall that coming from the server to the client via SocketIO, there are 3 events: ‘my socketId’, ‘all users’ and ‘message’. We have discussed the first 2. The last one is ‘message’
Hopefully you see that what I show here is taking a fairly difficult problem and implement in a very simple and easy-to-reason-about manner. SocketIO is the technology stack that enables the real-time aspect of web applications. RxJS takes events from SocketIO and turns them into observables. RxJS is also used to handle user interactions like key press and button click events in a form on a page. Last but not least, this chat room application shows how to embrace unidirectional data flow and we always re render by calling setState whenever there is a data change. Through the magical diff algorithm of ReactJS, the data changes happen seamlessly on the page.
The live demo runs on my DigitalOcean VPS. I host several applications on it thanks to using bouncy and forever. If you’re interested, you can take a look at how I host multiple applications on NodeJS here.
If you find this post helpful or useful, like it on Twitter, follow me and re-tweet.