惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Forbes - Security
Forbes - Security
大猫的无限游戏
大猫的无限游戏
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Jina AI
Jina AI
美团技术团队
博客园 - 聂微东
博客园 - 叶小钗
Security Latest
Security Latest
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
博客园_首页
Spread Privacy
Spread Privacy
J
Java Code Geeks
雷峰网
雷峰网
宝玉的分享
宝玉的分享
C
Cyber Attacks, Cyber Crime and Cyber Security
P
Privacy International News Feed
C
CXSECURITY Database RSS Feed - CXSecurity.com
T
Threat Research - Cisco Blogs
The Hacker News
The Hacker News
量子位
L
LINUX DO - 热门话题
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
G
GRAHAM CLULEY
D
Darknet – Hacking Tools, Hacker News & Cyber Security
月光博客
月光博客
腾讯CDC
Last Week in AI
Last Week in AI
人人都是产品经理
人人都是产品经理
酷 壳 – CoolShell
酷 壳 – CoolShell
T
Tor Project blog
罗磊的独立博客
V
Vulnerabilities – Threatpost
Apple Machine Learning Research
Apple Machine Learning Research
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
有赞技术团队
有赞技术团队
Project Zero
Project Zero
Hugging Face - Blog
Hugging Face - Blog
爱范儿
爱范儿
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
T
Tenable Blog
MyScale Blog
MyScale Blog
T
The Exploit Database - CXSecurity.com
GbyAI
GbyAI
博客园 - 【当耐特】
O
OpenAI News
Schneier on Security
Schneier on Security
S
Secure Thoughts
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
S
Securelist
博客园 - 司徒正美

Minko Gechev's blog

skillgrade Unit Tests for AI Agent Skills You Should Care About AI Generative Development LLM-first Web Framework Reactive framework in ~200 lines of JavaScript Managing Angular Are LLMs going to replace us? Prefetching Heuristics Design Patterns in Open Source Projects - Part II Design Patterns in Open Source Projects - Part I What I learned doing 125 public talks - Part I Dynamic imports solve all the problems, right? 5 Angular CLI Features You Didn't Know About Angular quicklink Preloading Strategy Introducing Bazel Schematics for Angular CLI Building TypeScript Projects with Bazel Joining Google Playing Mortal Kombat with TensorFlow.js. Transfer learning and data augmentation Fast, extensible, configurable, and beautiful linter for Go Introducing Guess.js - a toolkit for enabling data-driven user-experiences on the Web Machine Learning-Driven Bundling. The Future of JavaScript Tooling. JavaScript Decorators for Declarative and Readable Code 3 Tricks For Using Redux and Immutable.js with TypeScript Follow Your Dream Career with Open Source. Personal Story. Redux Anti-Patterns - Part 1. State Management. Faster Angular Applications - Understanding Differs. Developing a Custom IterableDiffer Faster Angular Applications - Part 2. Pure Pipes, Pure Functions and Memoization Faster Angular Applications - Part 1. On Push Change Detection and Immutability Understanding Dynamic Scoping and TemplateRef Implementing a Simple Compiler on 25 Lines of JavaScript Developing Statically Typed Programming Language WebVR for a Gamified IDE 7 Angular Tools That You Should Consider Announcing ngrev - Reverse Engineering Tool for Angular Implementing Angular's Dependency Injection in React. Understanding Element Injectors. Distributing an Angular Library - The Brief Guide Angular in Production Ahead-of-Time Compilation in Angular 2.5X Smaller Angular 2 Applications with Google Closure Compiler Using Stripe with Angular (Deprecated) Building an Angular Application for Production Implementing the Missing "resolve" Feature of the Angular 2 Router Scalable Single-Page Application Architecture Managing ambient type definitions and dealing with the "Duplicate identifier" TypeScript error Static Code Analysis of Angular 2 and TypeScript Projects Enforcing Best Practices with Static Code Analysis of Angular 2 Projects ViewChildren and ContentChildren in Angular Dynamically Configuring the Angular's Router Angular 2 Hot Loader Lazy Loading of Route Components in Angular 2 Aspect-Oriented Programming in JavaScript Flux in Depth. Store and Network Communication. Using JSX with TypeScript Flux in Depth. Overview and Components. Even Faster AngularJS Data Structures Boost the Performance of an AngularJS Application Using Immutable Data - Part 2 Angular2 - First Impressions Build Your own Simplified AngularJS in 200 Lines of JavaScript Persistent State of ReactJS Component Boost the Performance of an AngularJS Application Using Immutable Data Processing Binary Protocols with Client-Side JavaScript Stream your Desktop to HTML5 Video Element Multi-User Video Conference with WebRTC Asynchronous calls with ES6 generators Binary Tree iterator with ES6 generators AngularJS in Patterns (Part 3) AngularJS in Patterns (Part 2). Services. Using GitHub Pages with Jekyll! AngularJS in Patterns (Part 1). Overview of AngularJS Singleton in JavaScript Express over HTTPS What I get from the JavaScript MV* frameworks Remote Desktop Client with AngularJS and Yeoman The magic of $resource (or simply a client-side Active Record) AngularJS Inheritance Patterns AngularAOP v0.1.0 Advanced JavaScript at Sofia University AngularJS style guide Lazy prefetching of AngularJS partials VNC client on 200 lines of JavaScript Aspect-Oriented Programming with AngularJS CSS3 flipping effect Practical programming with JavaScript Why I should use publish/subscribe in JavaScript JavaScript, the weird parts Functional programming with JavaScript plainvm Looking for performance? Probably you should NOT use [].sort (V8) JavaScript image scaling ELang Caching CSS with localStorage Self-invoking functions in JavaScript (or Immediately Invoked Function Expressions) Asus N56VZ + Ubuntu 12.04 (en) Asus N56VZ + Ubuntu 12.04 Debian Squeeze + LXDE on Google Nexus S (or having some fun while suffering) HTML5 image editor Курсови проекти – ФМИ Carousel Gallery SofiaJS...
WebRTC chat with React.js
2014-09-03 · via Minko Gechev's blog

Edit · Sep 3, 2014 · 14 minutes read · Follow @mgechev React.js JavaScript WebRTC p2p Peer to Peer

In this blog post I’m going to share how could be build WebRTC chat with React.js. Before we continue lets describe briefly what React.js and WebRTC are.

The application from this tutorial is available at GitHub.

React.js

React.js is reactive JavaScript framework, which helps you to build user interface. Facebook states that we can think of React as the “V” in MVC. React’s main aspect is the state. When the state of the application changes this automatically propagates through the application’s components. A React component is a self-contained module, which is composed by one or more other components. Usually the component depends on state, which is being provided by a parent component. May be the explanation seems quite abstract now, but during the tutorial the picture will get much more clear.

WebRTC

RTC stands for Real-Time Communication. Until browsers implemented WebRTC our only way to provide communication between several browsers was to proxy the messages via a server between them (using WebSockets or HTTP). WebRTC makes the peer-to-peer communication between browsers possible. Using the NAT traversal framework - ICE, we are able find the most appropriate route between the browsers and make them communicate without mediator. Since 1st of July 2014, v1.0 of the WebRTC browser APIs standard is already published by W3C.

NAT

Before we continue with the tutorial, lets say a few words about what NAT is. NAT stands for Network Address Translation. It is quite common way for translating internal (private) IP addresses to public ones and vice verse. A lot of ISP providers with limited capacity of public IP addresses uses this way of scaling using private IP addresses in their internal networks and translating them to public addresses visible to the outside world. More about NAT and the different types of NAT could be read in this wiki article.

Implementation

Now lets get starting with the actual implementation of our WebRTC based chat.

Architecture

High-level overview

Since I’m kind of traditionalist I’ll start by providing a basic, high-level overview of the architecture of our p2p (peer to peer) chat.

Architecture

The dashed arrows, in the diagram, indicate signaling WebSocket connections. Each client initiates such connection with the server. With these connections each client aims to register itself on the server and use the server as a proxy during the NAT traversal procedures, defined by the signaling protocol (for now we can think of the signaling protocol as SIP or XMPP Jingle). Actually the signaling protocol in our case is provided by Peer.js.

The solid arrow stands for peer-to-peer TCP or UDP (TCP in our case) data channel between the browsers. We use full mesh, which scales badly especially when we use video or audio streaming. For the purpose of our chat full mesh is good enough.

Low-level overview

In the beginning of the blog post I mentioned that React.js application contains a finite amount of React.js components composed together. In this subsection I’ll illustrate, which are the different components of our application and how they are composed together. The diagram below isn’t following the UML standard, it only illustrate, as clearly as possible, our micro-architecture.

Micro-architecture

Lets concentrate on the left-hand side of the diagram. As you see we have a set of nested components. The most outer, non-named, component (the rectangle, which contains all other rectangles), is the ChatBox component. In its left-hand side is positioned the MessagesList component, which is composition of ChatMessage components. Each ChatMessage component contains a different chat message, which has author, date when published and content. On the right-hand side of the ChatBox is positioned the UsersList component. This component lists all users, which are currently in the chat session. The last component is the MessageInput component. The MessageInput component is a simple text input, which once detect a press of the Enter key triggers an event, with data - its value.

The ChatBox component uses ChatProxy. The ChatProxy is responsible for registering the current client on the server and talking with the other peers. For simplicity I’ve used Peer.js, which provides nice high-level API, wrapping the browser’s WebRTC API.

Getting started

In this section we are going to setup our project…

Create a directory called react-p2p and enter it:

mkdir react-p2p && cd react-p2p

Create a package.json file with the content:

{
  "name": "react-peerjs",
  "version": "0.0.0",
  "description": "ReactJS chat with PeerJS",
  "main": "index.js",
  "scripts": {
    "test": ""
  },
  "keywords": [
    "webrtc",
    "nodejs",
    "react",
    "javascript",
    "awesome"
  ],
  "author": "mgechev",
  "license": "MIT",
  "dependencies": {
    "express": "~4.8.4",
    "peer": "~0.2.6",
    "socket.io": "~1.0.6"
  }
}

This file defines primitive information for our server, like name, version, keywords and dependencies. The dependencies of our server are:

  • express - we are going to use express as a static server
  • peer - A server, which implements the signaling of our application
  • socket.io

Now lets take a look at ./bower.json:

{
  "name": "react-peerjs",
  "main": "index.js",
  "version": "0.0.0",
  "authors": [
    "mgechev"
  ],
  "license": "MIT",
  "ignore": [
    "**/.*",
    "node_modules",
    "bower_components",
    "public/lib",
    "test",
    "tests"
  ],
  "dependencies": {
    "react": "~0.11.1",
    "jquery": "~2.1.1",
    "bootstrap": "~3.2.0",
    "eventEmitter": "~4.2.7",
    "peerjs": "~0.3.9"
  }
}
The bower.json file defines primitive information and dependencies for the client-side of the application. The required dependencies are:
  • react - the framework, we are going to use for building our UI.
  • eventEmitter - our components are going to fire events, which later are going to be handled by other components. EventEmitter will be used as based class for our “event-driven” components.
  • peerjs - wraps the browser’s WebRTC API into high-level, easier to use API.
  • jquery
  • bootstrap

And… .bowerrc

{
  "directory": "public/lib"
}

In .bowerrc we define that we want all bower dependencies to be saved at /public/lib.

Now, in order to resolve all dependencies, run:

bower install && npm install

Now lets start with our implementation.

Server-side

We have a few lines of Node.js, which are required for signaling and establishing p2p connection between the peers.

Create a file called index.js in the root of our application and add the following content:

var PeerServer = require('peer').PeerServer,
    express = require('express'),
    Topics = require('./public/src/Topics.js'),
    app = express(),
    port = process.env.PORT || 3001;

app.use(express.static(__dirname + '/public'));

var expressServer = app.listen(port);
var io = require('socket.io').listen(expressServer);

console.log('Listening on port', port);

var peerServer = new PeerServer({ port: 9000, path: '/chat' });

peerServer.on('connection', function (id) {
  io.emit(Topics.USER_CONNECTED, id);
  console.log('User connected with #', id);
});

peerServer.on('disconnect', function (id) {
  io.emit(Topics.USER_DISCONNECTED, id);
  console.log('User disconnected with #', id);
});

In the snippet above, we create a simple express server, which servers static files from the directory /public, located in the root folder. After that we create a PeerServer, which on the other hand is responsible for handling the signaling between the different peers. In our case we can think of the PeerServer and the protocol, which it implements as alternative of SIP or XMPP Jingle.

Once our PeerServer detects that a peer has been connected to it, it triggers the event USER_CONNECTED to all peers. Once a client disconnects from the PeerServer we trigger USER_DISCONNECTED. These two events are very important for handling the list of currently available users.

Client-side

ChatProxy.js

Now lets take a look at the component responsible for communication between our peers and registering them on the server.

The biggest advantage of putting the logic for p2p communication and signaling out of the react components is achieving separation of concerns. This way we achieve highly coherent components, which are reusable and testable.

Inside /public/src/models/ create a file called ChatProxy.js.

function ChatProxy() {
  EventEmitter.call(this);
  this._peers = {};
}

ChatProxy.prototype = Object.create(EventEmitter.prototype);

Our ChatProxy extends EventEmitter. We use inheritance because we want to reuse the functionality provided by the EventEmitter and fire events when we receive new message, client connects or disconnects.

The most complex method, which ChatProxy implements is the connect method. Lets take a look at it:

ChatProxy.prototype.connect = function (username) {
  var self = this;
  this.setUsername(username);
  this.socket = io();
  this.socket.on('connect', function () {
    self.socket.on(Topics.USER_CONNECTED, function (userId) {
      if (userId === self.getUsername()) {
        return;
      }
      self._connectTo(userId);
      self.emit(Topics.USER_CONNECTED, userId);
      console.log('User connected', userId);
    });
    self.socket.on(Topics.USER_DISCONNECTED, function (userId) {
      if (userId === self.getUsername()) {
        return;
      }
      self._disconnectFrom(userId);
      self.emit(Topics.USER_DISCONNECTED, userId);
      console.log('User disconnected', userId);
    });
  });
  console.log('Connecting with username', username);
  this.peer = new Peer(username, {
    host: location.hostname, port: 9000, path: '/chat'
  });
  this.peer.on('open', function (userId) {
    self.setUsername(userId);
  });
  this.peer.on('connection', function (conn) {
    self._registerPeer(conn.peer, conn);
    self.emit(Topics.USER_CONNECTED, conn.peer);
  });
};

If the client have passed username to the connect call we set the current username, after that with io() we establish new socket.io connection. The socket.io connection is going to be used for receiving USER_CONNECTED and USER_DISCONNECTED events. Once we have been connected to the socket.io server, we bind to these events. We need extra, socket.io, connection here because the API of Peer.js doesn’t provide all required events by its public API.

In the snippet:

self.socket.on(Topics.USER_CONNECTED, function (userId) {
  if (userId === self.getUsername()) {
    return;
  }
  self._connectTo(userId);
  self.emit(Topics.USER_CONNECTED, userId);
  console.log('User connected', userId);
});

Once we receive event, which indicates that new user is connected, we make sure that the connected peer is not us. In this case, we establish connection with it by calling the “private” method _connectTo.

The callback for USER_DISCONNECTED is almost analogous so we won’t take a further look at it.

The next interesting part of the connect method is the snippet where we establish new Peer.js connection:

this.peer = new Peer(username, {
  host: location.hostname, port: 9000, path: '/chat'
});
this.peer.on('open', function (userId) {
  self.setUsername(userId);
});
this.peer.on('connection', function (conn) {
  self._registerPeer(conn.peer, conn);
  self.emit(Topics.USER_CONNECTED, conn.peer);
});

Once we invoke the constructor function Peer, provided by Peer.js, with the appropriate parameters, we bind to the open event. When the callback passed for the open event is being invoked, we receive the unique identifier of the current user, in the ideal case it will be the username entered in the home screen. Once we receive the user identifier we can save it.

When we receive connection event we register the connected peer and emit USER_CONNECTED event. The USER_CONNECTED event will be handled by the ChatBox, which will lead to change of the state of the UI.

The full content of ChatProxy could be found at GitHub.

app.jsx

The initial view of the user would be:

<section id="container">
  <div class="reg-form-container">
    <label for="username-input">Username</label>
    <input type="text" id="username-input" class="form-control">
    <br>
    <button id="connect-btn" class="btn btn-primary">Connect</button>
  </div>
</section>

Once rendered in the browser, this would be a simple text box asking the client for optional username. In order to see what happens once the user click on the #connect-btn, lets take a look at the app.jsx file, which is located at /public/app.jsx:

/** @jsx React.DOM */

$(function () {
  $('#connect-btn').click(function () {
    initChat($('#container')[0],
      $('#username-input').val());
  });

  function initChat(container, username) {
    React.renderComponent(<ChatBox chatProxy={new ChatProxy()}
      username={username}></ChatBox>, container);
  }

  window.onbeforeunload = function () {
    return 'Are you sure you want to leave this page?';
  };

});

When the user clicks on #connect-btn we render the ChatBox component inside the #container element. So now lets see what the ChatBox does:

ChatBox.jsx

At /public/src/components/chat/ create a file called ChatBox.jsx and add the following content:

/** @jsx React.DOM */

'use strict';

var ChatBox = React.createClass({
  getInitialState: function () {
    return { users: [] };
  },

  componentDidMount: function () {
    this.chatProxy = this.props.chatProxy;
    this.chatProxy.connect(this.props.username);
    this.chatProxy.onMessage(this.addMessage.bind(this));
    this.chatProxy.onUserConnected(this.userConnected.bind(this));
    this.chatProxy.onUserDisconnected(this.userDisconnected.bind(this));
  },

  userConnected: function (user) {
    var users = this.state.users;
    users.push(user);
    this.setState({
      users: users
    });
  },

  userDisconnected: function (user) {
    var users = this.state.users;
    users.splice(users.indexOf(user), 1);
    this.setState({
      users: users
    });
  },

  messageHandler: function (message) {
    message = this.refs.messageInput.getDOMNode().value;
    this.addMessage({
      content: message,
      author : this.chatProxy.getUsername()
    });
    this.chatProxy.broadcast(message);
  },

  addMessage: function (message) {
    if (message) {
      message.date = new Date();
      this.refs.messagesList.addMessage(message);
    }
  },

  render: function () {
    return (
      <div className="chat-box" ref="root">
        <div className="chat-header ui-widget-header">React p2p Chat</div>
        <div className="chat-content-wrapper row">
          <MessagesList ref="messagesList"></MessagesList>
          <UsersList users={this.state.users} ref="usersList"></UsersList>
        </div>
        <MessageInput
          ref="messageInput"
          messageHandler={this.messageHandler}>
        </MessageInput>
      </div>
    );
  }
});

Lets take a look at the render method:

render: function () {
  return (
    <div className="chat-box" ref="root">
      <div className="chat-header ui-widget-header">React p2p Chat</div>
      <div className="chat-content-wrapper row">
        <MessagesList ref="messagesList"></MessagesList>
        <UsersList users={this.state.users} ref="usersList"></UsersList>
      </div>
      <MessageInput
        ref="messageInput"
        messageHandler={this.messageHandler}>
      </MessageInput>
    </div>
  );
}

The render method returns the markup, which should be rendered. We use components, which are already defined and available in the given scope (components like MessagesList and MessageInput).

Once the component has been mounted the componentDidMount method is being invoked:

componentDidMount: function () {
  this.chatProxy = this.props.chatProxy;
  this.chatProxy.connect(this.props.username);
  this.chatProxy.onMessage(this.addMessage.bind(this));
  this.chatProxy.onUserConnected(this.userConnected.bind(this));
  this.chatProxy.onUserDisconnected(this.userDisconnected.bind(this));
},

In this method we create new ChatProxy, invoke its method connect and add event handlers. Once we receive a new message the callback registered for onMessage will be invoked, once a user is connected the callback userConnected will be invoked and once a peer is being disconnected the callback userDisconnected will be invoked. We use Function.prototype.bind in order to change the context for the callbacks with appropriate one.

userConnected and userDisconnected are similar:

userConnected: function (user) {
  var users = this.state.users;
  users.push(user);
  this.setState({
    users: users
  });
},

userDisconnected: function (user) {
  var users = this.state.users;
  users.splice(users.indexOf(user), 1);
  this.setState({
    users: users
  });
}

They both change the state, which leads to call of the render method with the new state, which reflects on other components and respectively on the current UI.

In the addMessage method we have:

addMessage: function (message) {
  if (message) {
    message.date = new Date();
    this.refs.messagesList.addMessage(message);
  }
}

The interesting part here is the line: this.refs.messagesList.addMessage(message);, where we use this.refs. This is built-in React.js feature, which allows us to reference to existing child components. Once we set the ref attribute of given component (like <MessagesList ref="messagesList"></MessagesList>) we can later access the component by using this.refs.REF_ATTRIBUTE_VALUE.

MessagesList.jsx

Inside /public/src/components/chat/ add file called MessagesList.jsx and add the following content:

/** @jsx React.DOM */

'use strict';

var MessagesList = React.createClass({

  getInitialState: function () {
    return { messages: [] };
  },

  addMessage: function (message) {
    var messages = this.state.messages,
        container = this.refs.messageContainer.getDOMNode();
    messages.push(message);
    this.setState({ messages: messages });
    // Smart scrolling - when the user is
    // scrolled a little we don't want to return him back
    if (container.scrollHeight -
        (container.scrollTop + container.offsetHeight) >= 50) {
      this.scrolled = true;
    } else {
      this.scrolled = false;
    }
  },

  componentDidUpdate: function () {
    if (this.scrolled) {
      return;
    }
    var container = this.refs.messageContainer.getDOMNode();
    container.scrollTop = container.scrollHeight;
  },

  render: function () {
    var messages;
    messages = this.state.messages.map(function (m) {
      return (
        <ChatMessage message={m}></ChatMessage>
      );
    });
    if (!messages.length) {
      messages = <div className="chat-no-messages">No messages</div>;
    }
    return (
      <div ref="messageContainer" className="chat-messages col-xs-9">
        {messages}
      </div>
    );
  }
});

Lets take a look at the render method of this component:

render: function () {
  var messages;
  messages = this.state.messages.map(function (m) {
    return (
      <ChatMessage message={m}></ChatMessage>
    );
  });
  if (!messages.length) {
    messages = <div className="chat-no-messages">No messages</div>;
  }
  return (
    <div ref="messageContainer" className="chat-messages col-xs-9">
      {messages}
    </div>
  );
}

Initially we iterate over all messages from the state of the current component (this.state.messages). Using Array.prototype.map we turn our messages array into ChatMessages and later render them into the div.chat-messages.

In addMessage we add new chat messages by appending them to the list of all messages:

addMessage: function (message) {
  var messages = this.state.messages,
      container = this.refs.messageContainer.getDOMNode();
  messages.push(message);
  this.setState({ messages: messages });
  // Smart scrolling - when the user is
  // scrolled a little we don't want to return him back
  if (container.scrollHeight -
      (container.scrollTop + container.offsetHeight) >= 50) {
    this.scrolled = true;
  } else {
    this.scrolled = false;
  }
}

The interesting part here is:

if (container.scrollHeight -
    (container.scrollTop + container.offsetHeight) >= 50) {
  this.scrolled = true;
} else {
  this.scrolled = false;
}

Basically, this snippet checks whether the user have scrolled more than 50pxs. If he did, we don’t want to scroll to bottom once he have started reading messages from the history of the chat. Thats why depending on whether the user have or haven’t scrolled we set this.scrolled to true or false.

We use this.scrolled in componentDidUpdate:

componentDidUpdate: function () {
  if (this.scrolled) {
    return;
  }
  var container = this.refs.messageContainer.getDOMNode();
  container.scrollTop = container.scrollHeight;
}

Once the component is going to be updated (for example because of new message added), we check whether the user have scrolled and if he had, we set scrollTop to the appropriate value. For getting the scroll container we use this.refs, as explained above.

MessageInput.jsx

This is the last component we will look at.

/** @jsx React.DOM */

'use strict';

var MessageInput = React.createClass({

  mixins: [React.addons.LinkedStateMixin],

  keyHandler: function (event) {
    var msg = this.state.message.trim();
    if (event.keyCode === 13 && msg.length) {
      this.props.messageHandler(msg);
      this.setState({ message: '' });
    }
  },

  getInitialState: function () {
    return { message: '' };
  },

  render: function () {
    return (
      <input type="text"
        className = "form-control"
        placeholder="Enter a message..."
        valueLink={this.linkState("message")}
        onKeyUp={this.keyHandler}/>
    );
  }
});

In this component we use the mixin React.addons.LinkedStateMixin, which adds the method linkState to our component. Once the linkState method is called we can create two-way data binding between given input and property of our state. The name of the property depends on the value we pass to the linkState call. For example if we invoke this.linkState('value'), once the value of the input is being changed, this will reflect on this.state.value.

Another interesting moment here is the key handler. On key up of input.form-control the keyHandler method will be called. The method checks whether the event was called by pressing enter and whether the length of the trimmed value of the current message is more than zero, if it is, it updates the value of the current message to be the empty string and invokes this.props.messageHandler. this.props.messageHandler is passed by the ChatBox component as property of the MessageInput:

<MessageInput
  ref="messageInput"
  messageHandler={this.messageHandler}>
</MessageInput>

Run the project…

The next step is to run the project by:

node index.js && open http://localhost:3001

Fin!

I hope the blog post was fun and useful! :-)