React + Socket.IO Part I : Real-Time Chat Application

Real Time !

Web users have very little patience. This is one of the reasons we try to optimize for page load times and spend time making our applications feel fast. An increasingly popular way to make applications fast and decrease the work of our users is to use WebSockets.

WebSockets is an advanced technology that makes it possible to open an interactive communication session between the user's browser and a server. With this API, you can send messages to a server and receive event-driven responses without having to poll the server for a reply.

So what we are going to do in this post is build a basic chat application that allows users to send and receive messages to each other in real time. We will achieve this goal using the following technologies: React, Redux, Socket IO, and Express. Let's get started.

Configuring Our Express Server

The first thing we want to do is set up our Express server to handle the socket connection between the client and server. To set up the basics of our React app and Express server, we are going to use this awesome starter built by my dear friend Sophie. Clone down this repository so we can get started: https://github.com/SophieDeBenedetto/react-redux-starter-kit.

note: I'm writing this post assumming you know the basics of react, redux, and a little bit of express. Checkout the linked resources listed above if you need some help getting started with these technologies.

Run npm install and then npm start to make sure the starter is working properly. Once you get that up and running, add the following code to your server.js located inside the tools directory.

import express from 'express';  
import webpack from 'webpack';  
import path from 'path';  
import config from '../webpack.config.dev';  
import open from 'open';  
import socket from 'socket.io'  
import { Server } from 'http' 


const port = 3000;  
const app = express()  
const server = Server(app)  
const compiler = webpack(config)  
const io = socket(server) 


app.use(require('webpack-dev-middleware')(compiler, {  
  noInfo: true, publicPath: config.out.publicPath
}));

app.use(require('webpack-hot-middleware')(compiler));

app.use('*', function(req, res) {  
 res.sendFile(path.join(__dirname, '../src/index.html')); 
});

io.on('connection', function(socket) {  
  console.log('a user connected')
})

server.listen(port, function(err) {  
  if (err) { 
    console.log('error', err); 
   } else { 
      open(`http://localhost:${port}`);
   }
 });

Most of this code comes with the starter that you cloned down, but I added a few lines of code so let's talk about what we added and why they are important.

Socket - Server Side

Run the following code in your terminal: npm install --save socket.io

This is going to allow us to use the socket.io library, which is going to allow us to send messages from the client to the server without polling.

Here we are defining a variable io, setting it equal to a new instance of socket.io by passing it the server to listen on.

import socket from 'socket.io'  
import { Server } from 'http'

const io = socket(server)  

and here we are telling our socket to listen for any connections to the server and log a statement whenever a user connects.

io.on('connection', function(socket) {  
  console.log('a user connected')
}) 

I will explain what some of this code is doing after we set up the client side code.

Socket - Client Side

In your index.html file add the following code

<html>  
  <head>
     <meta charset="utf-8">
  </head>
  <body>
    <div id="main"></div>
    <script src="https://cdn.socket.io/socket.io-1.3.5.js"></script>
   <script> 
     var socket = io();
   </script> 
   <script src="/bundle.js"></script>
  </body>
</html>  

This code exposes io() (our socket) globally and that's all we need to do right now to set up our client side socket connection.

Let's fire up our application by running npm start. Because we used the npm package open, our app should open automatically if there were no problems during start up. If our connection is being made properly we should see a user connected in the terminal. We did it! If you are having any issues you can checkout my code here.

Socket Events

Now that we have our express server and client communicating, let's talk about the main idea behind Socket.IO, which is emitting and listening for events. When using Socket.IO you can send and receive any events you want, with almost any data you want. The two basic functions that we need to be concerned with now are emit() and on(). To send an event you will use emit() on the socket object, and to listen for events we will use on(). For example:

Our server might be listening for a message to be sent from the client like so:

io.on('connect', function(socket) {  
  socket.on('message', function(message) { 
    console.log('I received a message', message)
  }
})

and from our React App we might have something that looks like this to send the message to the server:

handleOnSubmit(ev) {  
  socket.emit('message', ev.target.value)
}

As you have probably figured out already, the first argument of on() and emit() is the name of the event. These event names are arbitrary strings with the exception of a few reserved words, such as disconnect and connection. Okay, on to the fun stuff.

Chat Container Component

The first thing we want to do on the React side is to allow a user to send messages and have those messages be sent to all other users on the same socket connection. For the sake of not making this post too long to read, I am going to gloss over a lot of the boilerplate and React set up. You can checkout the final code here if you need assistance setting up React.

Lets create our Chat Container:

class ChatContainer extends React.Component {  
  constructor(props) { 
    super(props) 
      this.state = { 
        input: ''
        messages: props.messages
      } 

   this.handleOnChange = this.handleOnChange.bind(this)
   this.handleOnSubmit = this.handleOnSubmit.bind(this)
   this._handleMessageEvent = this._handleMessageEvent.bind(this)
   }

  componentDidMount(){ 
    this._handleMessageEvent()
   } 

   _handleMessageEvent(){
     socket.on('chat message', (inboundMessage) => {
       this.props.newMessage({user: 'test_user', message: inboundMessage})
        })
     } 

   handleOnChange(ev) { 
     this.setState({ input: ev.target.value })
   }

   handleOnSubmit(ev) {
     ev.preventDefault() 
     socket.emit('chat message', { message: this.state.input })

       this.setState({ input: '' })
   } 

   render() {
     return ( 
       <ChatLog messages={this.props.messages} />

          <form> 
             <FormGroup> 
               <InputGroup>
                 <FormControl onChange={this.handleOnChange} value={this.state.input/>
                 <Button bsStyle="primary" type="submit" onClick={this.handleOnSubmit}> Send </Button>

                 </InputGroup>
               </FormGroup>
             </form> 
          )
        }
     }

function mapStateToProps(state, ownProps) {  
  return { messages: state.messages } 
 } 

function mapDispatchToProps(dispatch) {  
  return bindActionCreators({ newMessage: messagesActions.newMessage}, dispatch) 
} 

export default connect(mapStateToProps, mapDispatchToProps(ChatContainer)  

There's a lot of code here, so let's talk about it.

First, I want my container component to have local state to handle the form input field. I am using React-Bootstrap to "style" this application, so some of the components being called in this container might look unfamiliar, but they are descriptive enough that you can put it together ;).

When the form is submitted our handleOnSubmit function is triggered.

handleOnSubmit(ev) {  
  ev.preventDefault() 
  socket.emit('chat message', { message: this.state.input })

  this.setState({ input: '' })
} 

This function is going to take the message from the user and send that message to our server so that it can be sent to all the other users of the application. Let's jump over to our server so we can set up the event listener.

We are going to keep nesting socket events inside of io.on('connection'), which has the ability to send and receive messages for each socket connection.

io.on('connection', function(socket) {  
  socket.on('chat message', function(message) {
    socket.broadcast.emit('chat message', message)
   )}
}

Above, we have set up sending the message from the client to the server, and with this line:

socket.broadcast.emit('chat message', message)

we can broadcast the message back to the client through the socket connection.

Let's jump back to our ChatContainer component and talk about how we are going to handle this message. Here we have the function that will handle the chat message event.

_handleMessageEvent(){  
  socket.on('chat message', (inboundMessage) => {
    this.props.newMessage({user: 'test_user', message: inboundMessage})
    })
}

This method will be called in our componentDidMount lifecycle method. When the message is received, we dispatch our newMessage action, which will update our Redux Application State. Now, the users of our application will see the new messages broadcast to their feed instantaneously. Check out the final code to see what the applications reducers and action creators look like.

In Part II, I will be discussing joining rooms and allowing users to send images in the chat rooms.

Show Comments
comments powered by Disqus