├── .gitignore
├── .babelrc
├── CHECKS
├── app-entry.js
├── screencast.gif
├── config.js
├── index.html
├── .eslintrc
├── app-server.js
├── webpack.config.js
├── models
└── Message.js
├── package.json
├── README.md
└── app-client.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | public/dist
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015", "react", "stage-2"]
3 | }
--------------------------------------------------------------------------------
/CHECKS:
--------------------------------------------------------------------------------
1 | WAIT=30
2 | ATTEMPTS=10
3 | / React Chat App
4 |
5 |
--------------------------------------------------------------------------------
/app-entry.js:
--------------------------------------------------------------------------------
1 | // app-entry.js
2 | require('babel-register')
3 | require('./app-server.js')
--------------------------------------------------------------------------------
/screencast.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cosmicjs/cosmicapp-react-chat/master/screencast.gif
--------------------------------------------------------------------------------
/config.js:
--------------------------------------------------------------------------------
1 | // config.js
2 | export default {
3 | bucket: {
4 | slug: process.env.COSMIC_BUCKET || 'cosmic-js-chat',
5 | type_slug: 'messages',
6 | read_key: process.env.COSMIC_READ_KEY,
7 | write_key: process.env.COSMIC_WRITE_KEY
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | React Chat App
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "airbnb",
3 | "parser": "babel-eslint",
4 | "env": {
5 | "browser": true,
6 | "node": true
7 | },
8 | "plugins": [
9 | "react"
10 | ],
11 | "rules": {
12 | "react/jsx-no-bind": 0,
13 | "no-new": 0,
14 | "curly": [2, "multi-or-nest"],
15 | "comma-dangle": [2, "never"],
16 | "semi": 0,
17 | "camelcase": 0,
18 | "new-cap": 0,
19 | "strict": 0,
20 | "no-underscore-dangle": 0,
21 | "no-use-before-define": 0,
22 | "eol-last": 0,
23 | "quotes": [2, "single"],
24 | "jsx-quotes": 1,
25 | "react/jsx-no-undef": 1,
26 | "react/jsx-uses-react": 1,
27 | "react/jsx-uses-vars": 1
28 | }
29 | }
--------------------------------------------------------------------------------
/app-server.js:
--------------------------------------------------------------------------------
1 | // app-server.js
2 | import express from 'express'
3 | const app = express()
4 | // Set port
5 | app.set('port', process.env.PORT || 3000)
6 | // Static files
7 | app.use(express.static('public'))
8 | const http = require('http').Server(app)
9 | const io = require('socket.io')(http)
10 |
11 | // Config
12 | import config from './config'
13 |
14 | // Models
15 | import Message from './models/Message'
16 |
17 | // Listen for a connection
18 | io.on('connection', socket => {
19 | // Create message
20 | socket.on('chat message', params => {
21 | Message.create(config, params, (message) => {
22 | io.emit('chat message', message);
23 | })
24 | })
25 | })
26 |
27 | // Route
28 | app.get('/', (req, res) => {
29 | res.sendFile(__dirname + '/index.html')
30 | })
31 |
32 | http.listen(app.get('port'), () => {
33 | console.log('React Chat App listening on ' + app.get('port'))
34 | })
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | // webpack.config.js
2 | var webpack = require('webpack')
3 |
4 | var loaders = [];
5 | // JS loaders
6 | if (process.env.NODE_ENV === 'development') {
7 | var js_loaders = {
8 | test: /\.js$/,
9 | loaders: ['react-hot','babel'],
10 | exclude: /node_modules/
11 | }
12 | } else {
13 | var js_loaders = {
14 | test: /\.js$/,
15 | loaders: ['babel'],
16 | exclude: /node_modules/
17 | }
18 | }
19 | loaders.push(js_loaders)
20 | // Development loaders
21 | if(process.env.NODE_ENV === 'development'){
22 | var es_lint = {
23 | test: /\.js$/,
24 | loader: 'eslint-loader',
25 | exclude: /node_modules/
26 | }
27 | loaders.push(es_lint)
28 | }
29 | module.exports = {
30 | devtool: 'source-map',
31 | entry: './app-client.js',
32 | output: {
33 | path: __dirname + '/public/dist',
34 | filename: 'bundle.js',
35 | publicPath: '/dist/'
36 | },
37 | module: {
38 | loaders: loaders
39 | },
40 | plugins: [
41 | new webpack.DefinePlugin({
42 | 'process.env.COSMIC_BUCKET': JSON.stringify(process.env.COSMIC_BUCKET),
43 | 'process.env.APP_URL': JSON.stringify(process.env.APP_URL)
44 | })
45 | ]
46 | };
47 |
--------------------------------------------------------------------------------
/models/Message.js:
--------------------------------------------------------------------------------
1 | // models/Message.js
2 | import Cosmic from 'cosmicjs'
3 |
4 | export default {
5 | create: (config, params, callback) => {
6 | const object = {
7 | title: params.author + ': ' + params.message,
8 | type_slug: config.bucket.type_slug,
9 | metafields: [
10 | {
11 | title: 'Message',
12 | key: 'message',
13 | value: params.message,
14 | type: 'textarea',
15 | edit: 1
16 | },
17 | {
18 | title: 'Author',
19 | key: 'author',
20 | value: params.author,
21 | type: 'text',
22 | edit: 1
23 | }
24 | ],
25 | options: {
26 | slug_field: 0,
27 | content_editor: 0,
28 | add_metafields: 0,
29 | metafields_title: 0,
30 | metafields_key: 0
31 | }
32 | }
33 | Cosmic.addObject(config, object, (err, res) => {
34 | const new_object = res.object
35 | const message = {
36 | _id: new_object._id,
37 | metafield: {
38 | message: {
39 | value: new_object.metafields[0].value
40 | },
41 | author: {
42 | value: new_object.metafields[1].value
43 | }
44 | }
45 | }
46 | callback(message)
47 | })
48 | }
49 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cosmic-js-chat",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "app-client.js",
6 | "dependencies": {
7 | "babel": "^6.3.26",
8 | "babel-core": "^6.4.5",
9 | "babel-loader": "^6.2.1",
10 | "babel-preset-es2015": "^6.3.13",
11 | "babel-preset-react": "^6.3.13",
12 | "babel-preset-stage-2": "^6.3.13",
13 | "babel-register": "^6.4.3",
14 | "cosmicjs": "^2.0.0",
15 | "express": "^4.13.3",
16 | "lodash": "^4.0.0",
17 | "node-uuid": "^1.4.7",
18 | "react": "^0.14.1",
19 | "react-bootstrap": "^0.28.2",
20 | "react-dom": "^0.14.1",
21 | "shorti": "^1.1.3",
22 | "socket.io": "^1.3.7",
23 | "socket.io-client": "^1.3.7",
24 | "webpack": "^1.12.2"
25 | },
26 | "scripts": {
27 | "start": "npm run production",
28 | "development": "npm run webpack-dev-server",
29 | "production": "webpack -p && node app-entry.js",
30 | "server": "nodemon app-entry.js",
31 | "webpack-dev-server": "NODE_ENV=development webpack-dev-server --hot --inline --devtool inline-source-map --history-api-fallback"
32 | },
33 | "author": "",
34 | "license": "ISC",
35 | "devDependencies": {
36 | "babel-eslint": "^5.0.0-beta6",
37 | "babel-preset-react": "^6.3.13",
38 | "eslint": "^1.10.3",
39 | "eslint-config-airbnb": "^3.0.2",
40 | "eslint-loader": "^1.2.0",
41 | "eslint-plugin-react": "^3.14.0",
42 | "react-hot-loader": "^1.3.0",
43 | "webpack-dev-server": "^1.12.1"
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## React Chat App
2 | 
3 |
4 | This is an example of a basic real time chat app using React, Socket.io and Cosmic JS. This example consists of the following:
5 |
6 | 1. [React](https://facebook.github.io/react/) for UI
7 | 2. [Babel](https://babeljs.io/) for ES6 and JSX transformation
8 | 3. [Webpack](https://webpack.github.io/) for bundling
9 | 4. [Socket.io](http://socket.io/) for real-time communication
10 | 5. [Cosmic JS](https://cosmicjs.com) for saving and returning messages from a cloud-hosted API
11 |
12 | The following dev tools are used:
13 |
14 | 1. [ESLint](http://eslint.org/) to make sure our code is consistent
15 | 2. [React Hot Loader](https://github.com/gaearon/react-hot-loader) for instant updates on save
16 |
17 | ### Install
18 | Run the following commands to install the app:
19 | ```
20 | git clone https://github.com/tonyspiro/react-chat-app
21 | cd react-chat-app
22 | npm install
23 | ```
24 | #### Run in production
25 | Run the following command to run the app in production:
26 | ```
27 | npm start
28 | ```
29 | View the app running in production at [http://localhost:3000](http://localhost:3000)
30 |
31 | #### Run in development
32 | Run the following commands to run the app in development with hot reloading:
33 | ```
34 | npm start server
35 | ```
36 | and in another terminal tab run:
37 | ```
38 | npm run development
39 | ```
40 | View the app running in development at [http://localhost:8080](http://localhost:8080)
41 |
42 | ### Configure your own chat app
43 | 1. Set up a bucket in [Cosmic JS](https://cosmicjs.com) with an object type of `messages`.
44 | 2. Edit config.js:
45 | ```javascript
46 | // config.js
47 | export default {
48 | bucket: {
49 | slug: 'your-bucket-slug',
50 | type_slug: 'messages'
51 | },
52 | server: {
53 | host: process.env.APP_URL || 'http://localhost:3000'
54 | }
55 | }
56 | ```
57 |
--------------------------------------------------------------------------------
/app-client.js:
--------------------------------------------------------------------------------
1 | // app-client.js
2 | import React, { Component } from 'react'
3 | import { render } from 'react-dom'
4 | import Cosmic from 'cosmicjs'
5 | import io from 'socket.io-client'
6 | import config from './config'
7 | import uuid from 'node-uuid'
8 | import S from 'shorti'
9 | import _ from 'lodash'
10 | import { Input } from 'react-bootstrap'
11 |
12 | class App extends Component {
13 |
14 | constructor() {
15 | super()
16 | this.state = {
17 | data: {
18 | messages: []
19 | }
20 | }
21 | }
22 |
23 | componentDidMount() {
24 | let data = this.state.data
25 | setTimeout(() => {
26 | this.refs.author.refs.input.focus()
27 | }, 100)
28 | const socket = io()
29 | Cosmic.getObjects(config, (err, res) => {
30 | const messages = res.objects.type.messages
31 | if (messages) {
32 | messages.reverse()
33 | this.setState({
34 | data: {
35 | author: data.author,
36 | messages
37 | }
38 | })
39 | }
40 | })
41 | // Listen for messages coming in
42 | socket.on('chat message', message => {
43 | data = this.state.data
44 | const messages = this.state.data.messages
45 | if (data.author !== message.metafield.author.value) {
46 | messages.push(message)
47 | this.setState({
48 | data: {
49 | author: data.author,
50 | messages
51 | }
52 | })
53 | }
54 | })
55 | }
56 |
57 | componentDidUpdate() {
58 | if (this.refs.message)
59 | this.refs.message.refs.input.focus()
60 | if (this.refs.messages_scroll_area)
61 | this.refs.messages_scroll_area.scrollTop = this.refs.messages_scroll_area.scrollHeight
62 | }
63 |
64 | setAuthor() {
65 | const author = this.refs.author.refs.input.value.trim()
66 | if (!author)
67 | return
68 | this.refs.author.refs.input.value = ''
69 | const messages = this.state.data.messages
70 | this.setState({
71 | data: {
72 | author,
73 | messages
74 | }
75 | })
76 | }
77 |
78 | createMessage() {
79 | const data = this.state.data
80 | const messages = data.messages
81 | const socket = io()
82 | const message_text = this.refs.message.refs.input.value.trim()
83 | if (!message_text)
84 | return
85 | const message_emit = {
86 | message: message_text,
87 | author: data.author
88 | }
89 | // Send message out
90 | socket.emit('chat message', message_emit)
91 | // Render to browser
92 | const message_browser = {
93 | _id: uuid.v1(),
94 | metafield: {
95 | author: {
96 | value: data.author
97 | },
98 | message: {
99 | value: message_text
100 | }
101 | }
102 | }
103 | messages.push(message_browser)
104 | this.setState({
105 | data: {
106 | author: data.author,
107 | messages
108 | }
109 | })
110 | this.refs.message.refs.input.value = ''
111 | }
112 |
113 | handleSubmit(e) {
114 | e.preventDefault()
115 | const data = this.state.data
116 | if (data.author)
117 | this.createMessage()
118 | else
119 | this.setAuthor()
120 | }
121 |
122 | render() {
123 | const data = this.state.data
124 | let form_input
125 | if (!data.author) {
126 | form_input = (
127 |
128 | Hi, what is your name?
129 |
130 |
131 | )
132 | } else {
133 | form_input = (
134 |
135 | Hello { data.author }, type a message:
136 |
137 |
138 | )
139 | }
140 | const messages = data.messages
141 | let messages_list
142 | if (messages) {
143 | // order by created
144 | const sorted_messages = _.sortBy(messages, message => {
145 | return message.created
146 | })
147 | messages_list = sorted_messages.map(message_object => {
148 | if (message_object) {
149 | return (
150 |
151 | { message_object.metafield.author.value }
152 | { message_object.metafield.message.value }
153 |
154 | )
155 | }
156 | })
157 | }
158 | const scroll_area_style = {
159 | ...S('h-' + (window.innerHeight - 140)),
160 | overflowY: 'scroll'
161 | }
162 | return (
163 |
164 |
165 |
React Chat App
166 |
169 |
170 |
171 |
174 |
175 |
176 | )
177 | }
178 | }
179 | const app = document.getElementById('app')
180 | render(, app)
181 |
--------------------------------------------------------------------------------