Facebook Like Chat Application using React JS, PHP and jQuery

This is similar to the previous chat application i have created except this uses React for the UI instead of jQuery. React JS is really amazing in creating UI. React uses Virtual DOM instead of the real DOM to find the difference between an UI update and hits the original DOM with a very minial update. React Components provide a convenient way to express the UI design constructed.

Facebook Like Chat Application using ReactJS

Database Design

Database Schema

The database design is pretty much same as that is used in the previous chat application.

The FbChatMock class is same as the one previously used, so there is no change in that.

Chat/Index Page

This is where the chat is loaded. Remeber to pass the user_id in the url for setting the current chat user id.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Facebook Chat application using React JS, PHP and MySQL</title>
    <link href="style/core.css" rel="stylesheet" type="text/css" />
  </head>
  <body>
    <?php
    @session_start();
    
    $_SESSION['user_id'] = isset($_GET['user_id']) ? (int) $_GET['user_id'] : 0;
    
    // Load the messages initially
    require_once __DIR__ . '/../core/FbChatMock.php';
    $chat = new FbChatMock();
    $messages = $chat->getMessages();
    ?>
    <!-- This is the React main render container -->
    <div id="container"></div>
    <!-- Load JS at bottom for performance -->
    <script type="text/javascript" src="scripts/jquery-1.11.0.min.js"></script>
    <script type="text/javascript" src="scripts/react.js"></script>
    <script type="text/javascript" src="scripts/JSXTransformer.js"></script>
    <script type="text/jsx" src="scripts/react-chat.js"></script>
  </body>
</html>

The HTML is pretty much simple, because React will take care of the rest.

Ajax Components

There are two ajax components.

  1. get_messages.php - Get's the messages from the database and sends it in JSON format.
  2. add_msg.php - Add's a messsage to the database, fetches the messsages and sends it back as a JSON

get_messages.php

<?php
require_once __DIR__ . '/../../core/FbChatMock.php';

$chat = new FbChatMock();
$messages = $chat->getMessages();
$chat_converstaion = array();

if (!empty($messages)) {
  foreach ($messages as $message) {
    $chat_id = (int) $message['chat_id'];
    $msg = htmlentities($message['message'], ENT_NOQUOTES);
    $user_name = ucfirst($message['username']);
    $sent = date('F j, Y, g:i a', $message['sent_on']);
    $chat_converstaion[] = array (
        'username' => $user_name,
        'message' => $msg,
        'time' => $sent,
        'id' => $chat_id
    );
  }
  $response = json_encode($chat_converstaion);
} else {
  echo '<span style="margin-left: 25px;">No chat messages available!</span>';
}

header('Content-type: application/json');
echo $response;
?>

add_msg.php

<?php
session_start();

if (isset($_POST['msg'])) {
  require_once __DIR__ . '/../../core/FbChatMock.php';
  
  $userId = (int) $_SESSION['user_id'];
  // Escape the message string
  $msg = htmlentities($_POST['msg'],  ENT_NOQUOTES);
  
  $chat = new FbChatMock();
  $result = $chat->addMessage($userId, $msg);
  // Send back the updated messages
  $messages = $chat->getMessages();
  $chat_converstaion = array();

  if (!empty($messages)) {
    foreach ($messages as $message) {
      $chat_id = (int) $message['chat_id'];
      $msg = htmlentities($message['message'], ENT_NOQUOTES);
      $user_name = ucfirst($message['username']);
      $sent = date('F j, Y, g:i a', $message['sent_on']);
      $chat_converstaion[] = array (
          'username' => $user_name,
          'message' => $msg,
          'time' => $sent,
          'id' => $chat_id
      );
    }
    $response = json_encode($chat_converstaion);
  } else {
    echo '<span style="margin-left: 25px;">No chat messages available!</span>';
  }

  header('Content-type: application/json');
  echo $response;
}
?>

React JS

This the main React js script where all the rendering of the Chat components takes place. React allows us to express the design that we make and convert them to components that is finally rendered as a web component. Below is the component structure of the Chat application.

React Components Structure

-ChatContainer
    -ChatHeader
    -Messages
        -Row(Loop)
    -ChatFooter

/**
 * This is the chat header component,
 * Data inside this is static based on the browser url
 */
var ChatHeader = React.createClass({
  render: function() {
    // To display the username
    if (/user_id=1/i.test(window.location.search)) {
      name = 'John';
    } else {
      name = 'Scott';
    }
    
    return (
      <div className="msg-wgt-header">
        <a href="#">{name}</a>
      </div>
    );
  }
});

// This is the chat row component
var Row = React.createClass({
  render: function() {
    return (
      <div className="msg-row-container">
        <div className="msg-row">
          <div className="avatar"></div>
          <span className="user-label">
            <a href="#" className="chat-username">{this.props.username}</a> <span className="msg-time">{this.props.time}</span>
          </span><br/>
          {this.props.message}
        </div>
      </div>
    );
  }
});

/**
 * Messages component
 */
var Messages = React.createClass({
  render: function() {
    // This is how you set inline styles in React
    var inlineStyles = {
      height: '300px',
      overflowY: 'scroll'
    };
  
    // Loop through the list of chats and create array of Row components
    var Rows = this.props.datas.map(function(data) {
      return (
        <Row key={data.id} username={data.username} time={data.time} message={data.message} />
      )
    });
    
    return (
      <div style={inlineStyles}>
        {Rows}
      </div>
    );
  }
});

/**
 * This component contains the chat textarea
 */
var ChatFooter = React.createClass({
  // Message send event handler
  handleUserMessage: function(event) {
    // When shift and enter key is pressed
    if (event.shiftKey && event.keyCode === 13) {
      var msg = this.refs.textArea.getDOMNode().value.trim();
      if (msg !== '') {
        // call the sendmessages of ChatContainer throught the props
        this.props.sendMessage(msg);
      }
      // Prevent default and clear the textarea
      event.preventDefault();
      this.refs.textArea.getDOMNode().value = null;
    }
  },
  
  render: function() {
    return (
      <div className="msg-wgt-footer">
        <textarea id="chatMsg" ref="textArea" onKeyDown={this.handleUserMessage} placeholder="Type your message. Press shift + Enter to send" />
      </div>
    );
  }
});

var ChatContainer = React.createClass({
  // Load the initial chats
  getInitialState: function() {
    return {datas: []};
  },
  
  // Get's the list of messages from the server and set's the state,
  // so that it re-renders the Messages
  getMessages: function() {
    $.ajax({
      url: 'ajax/get_messages.php',
      dataType: 'json',
      success: function(data) {
        this.setState({datas: data});
      }.bind(this)
    });
  },
  
  // Will add a new message and update the messages list
  sendMessage: function(message) {
    var that = this;
    $.ajax({
      url: 'ajax/add_msg.php',
      method: 'post',
      dataType: 'json',
      data: {msg: message},
      success: function(data) {
        this.setState({datas: data});
      }.bind(this)
    });
  },
  
  componentDidMount: function() {
    // get the list of messages
    this.getMessages();
    // set the poll interval
    setInterval(this.getMessages, 5000);
  },
  
  render: function() {
    return (
      <div className="chat-container">
        <ChatHeader />
        <Messages datas={this.state.datas} />
        <ChatFooter sendMessage={this.sendMessage} />
      </div>
    );
  }
});

// React entry point
React.render(
  <ChatContainer />,
  document.getElementById('container')
);

CSS Style

core.css

.chat-container {
  bottom: 0px;
  width: 250px;
  position: fixed;
  z-index: 300;
  right: 50px;
  border-top-right-radius: 3px;
  border-top-left-radius: 3px;
  font-family: 'tahoma';
  font-size: 12px;
  border: 1px solid lightgray;
}

/* message box header styles*/

.msg-wgt-header {
  background-color: #6D84B4;
  border: 1px solid rgba(67, 106, 187, 0.76);
  border-top-right-radius: 3px;
  border-top-left-radius: 3px;
  color: white;
  text-align: center;
  height: 28px;
}

.msg-wgt-header a {
  text-decoration: none;
  font-weight: bold;
  color: white;
  vertical-align: middle;
}

/* message box body styles*/

.msg-row-container {
  border-bottom: 1px solid lightgray;
}

.msg-row {
  width: 100%;
  display: inline-block;
}

.message {
  margin-left: 40px;
}

/* Message box footer styles*/

.msg-wgt-footer {
  height: 42px;
}

.msg-wgt-footer textarea {
  width: 95%;
  font-family: 'tahoma';
  font-size: 13px;
  padding: 5px;
}

.user-label {
  font-size: 11px;
}

.chat-username {
  color: '#6D84B4';
}

.msg-time {
  font-size: 10px;
  float: right;
  color: gray;
}

.avatar {
  width: 30px;
  height: 30px;
  float: left;
  background: url('/chat/public/assets/chat_avatar.png');
  border: 1px solid lightgray;
}

If you have any queries leave a comment and subscribe to get more of updates like this.