├── .gitignore ├── README.md ├── app.js ├── client └── app.jsx ├── package.json ├── public ├── css │ └── style.css ├── index.html └── js │ └── app.js └── routes └── socket.js /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ReactJS Socket.io Chat Application 2 | 3 | See This Blog Post : [ReactJS and Socket.IO Chat Application](http://danialk.github.io/blog/2013/06/16/reactjs-and-socket-dot-io-chat-application/) 4 | 5 | ## Running it 6 | 7 | First, grab the dependencies: 8 | 9 | npm install 10 | 11 | Build the applicaiton 12 | 13 | npm run build 14 | 15 | Then run the app like so: 16 | 17 | npm start 18 | 19 | And navigate to `localhost:3000` and chat ! -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var express = require('express'); 8 | var http = require('http'); 9 | 10 | var socket = require('./routes/socket.js'); 11 | 12 | var app = express(); 13 | var server = http.createServer(app); 14 | 15 | /* Configuration */ 16 | app.set('views', __dirname + '/views'); 17 | app.use(express.static(__dirname + '/public')); 18 | app.set('port', 3000); 19 | 20 | if (process.env.NODE_ENV === 'development') { 21 | app.use(express.errorHandler({ dumpExceptions: true, showStack: true })); 22 | } 23 | 24 | /* Socket.io Communication */ 25 | var io = require('socket.io').listen(server); 26 | io.sockets.on('connection', socket); 27 | 28 | /* Start server */ 29 | server.listen(app.get('port'), function (){ 30 | console.log('Express server listening on port %d in %s mode', app.get('port'), app.get('env')); 31 | }); 32 | 33 | module.exports = app; 34 | -------------------------------------------------------------------------------- /client/app.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | 5 | var socket = io.connect(); 6 | 7 | var UsersList = React.createClass({ 8 | render() { 9 | return ( 10 |
11 |

Online Users

12 | 23 |
24 | ); 25 | } 26 | }); 27 | 28 | var Message = React.createClass({ 29 | render() { 30 | return ( 31 |
32 | {this.props.user} : 33 | {this.props.text} 34 |
35 | ); 36 | } 37 | }); 38 | 39 | var MessageList = React.createClass({ 40 | render() { 41 | return ( 42 |
43 |

Conversation:

44 | { 45 | this.props.messages.map((message, i) => { 46 | return ( 47 | 52 | ); 53 | }) 54 | } 55 |
56 | ); 57 | } 58 | }); 59 | 60 | var MessageForm = React.createClass({ 61 | 62 | getInitialState() { 63 | return {text: ''}; 64 | }, 65 | 66 | handleSubmit(e) { 67 | e.preventDefault(); 68 | var message = { 69 | user : this.props.user, 70 | text : this.state.text 71 | } 72 | this.props.onMessageSubmit(message); 73 | this.setState({ text: '' }); 74 | }, 75 | 76 | changeHandler(e) { 77 | this.setState({ text : e.target.value }); 78 | }, 79 | 80 | render() { 81 | return( 82 |
83 |

Write New Message

84 |
85 | 89 |
90 |
91 | ); 92 | } 93 | }); 94 | 95 | var ChangeNameForm = React.createClass({ 96 | getInitialState() { 97 | return {newName: ''}; 98 | }, 99 | 100 | onKey(e) { 101 | this.setState({ newName : e.target.value }); 102 | }, 103 | 104 | handleSubmit(e) { 105 | e.preventDefault(); 106 | var newName = this.state.newName; 107 | this.props.onChangeName(newName); 108 | this.setState({ newName: '' }); 109 | }, 110 | 111 | render() { 112 | return( 113 |
114 |

Change Name

115 |
116 | 120 |
121 |
122 | ); 123 | } 124 | }); 125 | 126 | var ChatApp = React.createClass({ 127 | 128 | getInitialState() { 129 | return {users: [], messages:[], text: ''}; 130 | }, 131 | 132 | componentDidMount() { 133 | socket.on('init', this._initialize); 134 | socket.on('send:message', this._messageRecieve); 135 | socket.on('user:join', this._userJoined); 136 | socket.on('user:left', this._userLeft); 137 | socket.on('change:name', this._userChangedName); 138 | }, 139 | 140 | _initialize(data) { 141 | var {users, name} = data; 142 | this.setState({users, user: name}); 143 | }, 144 | 145 | _messageRecieve(message) { 146 | var {messages} = this.state; 147 | messages.push(message); 148 | this.setState({messages}); 149 | }, 150 | 151 | _userJoined(data) { 152 | var {users, messages} = this.state; 153 | var {name} = data; 154 | users.push(name); 155 | messages.push({ 156 | user: 'APPLICATION BOT', 157 | text : name +' Joined' 158 | }); 159 | this.setState({users, messages}); 160 | }, 161 | 162 | _userLeft(data) { 163 | var {users, messages} = this.state; 164 | var {name} = data; 165 | var index = users.indexOf(name); 166 | users.splice(index, 1); 167 | messages.push({ 168 | user: 'APPLICATION BOT', 169 | text : name +' Left' 170 | }); 171 | this.setState({users, messages}); 172 | }, 173 | 174 | _userChangedName(data) { 175 | var {oldName, newName} = data; 176 | var {users, messages} = this.state; 177 | var index = users.indexOf(oldName); 178 | users.splice(index, 1, newName); 179 | messages.push({ 180 | user: 'APPLICATION BOT', 181 | text : 'Change Name : ' + oldName + ' ==> '+ newName 182 | }); 183 | this.setState({users, messages}); 184 | }, 185 | 186 | handleMessageSubmit(message) { 187 | var {messages} = this.state; 188 | messages.push(message); 189 | this.setState({messages}); 190 | socket.emit('send:message', message); 191 | }, 192 | 193 | handleChangeName(newName) { 194 | var oldName = this.state.user; 195 | socket.emit('change:name', { name : newName}, (result) => { 196 | if(!result) { 197 | return alert('There was an error changing your name'); 198 | } 199 | var {users} = this.state; 200 | var index = users.indexOf(oldName); 201 | users.splice(index, 1, newName); 202 | this.setState({users, user: newName}); 203 | }); 204 | }, 205 | 206 | render() { 207 | return ( 208 |
209 | 212 | 215 | 219 | 222 |
223 | ); 224 | } 225 | }); 226 | 227 | React.render(, document.getElementById('app')); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reactjs-socket-io-chat-app", 3 | "version": "0.6.9", 4 | "private": true, 5 | "scripts": { 6 | "build": "browserify ./client/app.jsx -t babelify --outfile ./public/js/app.js", 7 | "start": "node app.js" 8 | }, 9 | "dependencies": { 10 | "express": "^4.13.1", 11 | "react": "^0.13.3", 12 | "socket.io": "^1.3.5" 13 | }, 14 | "devDependencies": { 15 | "babelify": "^6.1.3", 16 | "browserify": "^10.2.6" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /public/css/style.css: -------------------------------------------------------------------------------- 1 | body{ 2 | width: 600px; 3 | margin: 0 auto; 4 | border: 5px solid tomato; 5 | } 6 | .users{ 7 | float : right; 8 | margin-right: 1.5em; 9 | } 10 | .messages { 11 | width: 400px; 12 | margin: 1em; 13 | border : 1px solid black; 14 | padding: 1em; 15 | } 16 | .message_form, 17 | .change_name_form{ 18 | padding: 1em; 19 | } -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | ReactJS Socket.io Chat Application 8 | 9 | 10 |
11 |
12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /routes/socket.js: -------------------------------------------------------------------------------- 1 | // Keep track of which names are used so that there are no duplicates 2 | var userNames = (function () { 3 | var names = {}; 4 | 5 | var claim = function (name) { 6 | if (!name || names[name]) { 7 | return false; 8 | } else { 9 | names[name] = true; 10 | return true; 11 | } 12 | }; 13 | 14 | // find the lowest unused "guest" name and claim it 15 | var getGuestName = function () { 16 | var name, 17 | nextUserId = 1; 18 | 19 | do { 20 | name = 'Guest ' + nextUserId; 21 | nextUserId += 1; 22 | } while (!claim(name)); 23 | 24 | return name; 25 | }; 26 | 27 | // serialize claimed names as an array 28 | var get = function () { 29 | var res = []; 30 | for (user in names) { 31 | res.push(user); 32 | } 33 | 34 | return res; 35 | }; 36 | 37 | var free = function (name) { 38 | if (names[name]) { 39 | delete names[name]; 40 | } 41 | }; 42 | 43 | return { 44 | claim: claim, 45 | free: free, 46 | get: get, 47 | getGuestName: getGuestName 48 | }; 49 | }()); 50 | 51 | // export function for listening to the socket 52 | module.exports = function (socket) { 53 | var name = userNames.getGuestName(); 54 | 55 | // send the new user their name and a list of users 56 | socket.emit('init', { 57 | name: name, 58 | users: userNames.get() 59 | }); 60 | 61 | // notify other clients that a new user has joined 62 | socket.broadcast.emit('user:join', { 63 | name: name 64 | }); 65 | 66 | // broadcast a user's message to other users 67 | socket.on('send:message', function (data) { 68 | socket.broadcast.emit('send:message', { 69 | user: name, 70 | text: data.text 71 | }); 72 | }); 73 | 74 | // validate a user's name change, and broadcast it on success 75 | socket.on('change:name', function (data, fn) { 76 | if (userNames.claim(data.name)) { 77 | var oldName = name; 78 | userNames.free(oldName); 79 | 80 | name = data.name; 81 | 82 | socket.broadcast.emit('change:name', { 83 | oldName: oldName, 84 | newName: name 85 | }); 86 | 87 | fn(true); 88 | } else { 89 | fn(false); 90 | } 91 | }); 92 | 93 | // clean up when a user leaves, and broadcast it to other users 94 | socket.on('disconnect', function () { 95 | socket.broadcast.emit('user:left', { 96 | name: name 97 | }); 98 | userNames.free(name); 99 | }); 100 | }; 101 | --------------------------------------------------------------------------------