├── .gitignore ├── README.md ├── package.json ├── public ├── favicon.ico ├── index.html └── manifest.json ├── src ├── App.js ├── components │ ├── Message.js │ ├── MessageList.js │ ├── NewRoomForm.js │ ├── RoomList.js │ └── SendMessageForm.js ├── config.js ├── index.js └── style.css └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Learning how to build a Chat App with React.js and ChatKit through Scrimba's tutorial 2 | - If you want to learn this tutorial, here is [the Scrimba's link](https://scrimba.com/g/greactchatkit). 3 | - They teach us about how to organize the components, named [Component architecture](https://scrimba.com/p/pbNpTv/cm2a6f9), which is very helpful for developers' vision. 4 | 5 | # Below, I noted down all the steps as I went through the tutorial 6 | ## Step 1: Create components and import components in app.js 7 | - In app.js 8 | ``` 9 | import React from 'react' 10 | import MessageList from './components/MessageList' 11 | import SendMessageForm from './components/SendMessageForm' 12 | import RoomList from './components/RoomList' 13 | import NewRoomForm from './components/NewRoomForm' 14 | 15 | class App extends React.Component { 16 | render() { 17 | return ( 18 |
19 | 20 | 21 | 22 | 23 |
24 | ); 25 | } 26 | } 27 | 28 | export default App 29 | ``` 30 | 31 | ## Step 2: Build a basic MessageList.js 32 | ``` 33 | import React from 'react' 34 | 35 | //We const DUMMY_DATA as a test in rendering message-list to make sure the Message-List will work 36 | const DUMMY_DATA = [ 37 | { 38 | senderId: 'perborgen', 39 | text: 'Hey, how is it going?' 40 | }, 41 | { 42 | senderId: 'janedoe', 43 | text: 'Great! How about you?' 44 | }, 45 | { 46 | senderId: 'perborgen', 47 | text: 'Good to hear! I am great as well' 48 | } 49 | ] 50 | 51 | class MessageList extends React.Component { 52 | render() { 53 | return ( 54 |
55 | //This is JSX index and to write between JSX
(we cannot write JS) => we need to break it by curlybrackets {} 56 | {DUMMY_DATA.map((message, index) => { 57 | return ( 58 |
//each child needs to have a unique "key" prop 59 |
{message.senderId}
60 |
{message.text}
61 |
62 | ) 63 | })} 64 |
65 | ) 66 | } 67 | } 68 | 69 | export default MessageList 70 | ``` 71 | > // We cannot render 2 JSX div tags next to each other without a BIG div surrounding those 2 JSX because it will report error: Adjacent JSX elements must be wrapped in an enclosing tag => that is why we have div key={index} className="message" and /div wrapped around div className="message-username" and div className="message-text" 72 | 73 | ## Step 3: How to use ChatKit and connect to ChatKit's API to fetch out messages from a given room 74 | - Go to https://pusher.com/ 75 | - Product -> ChatKit -> Sign Up for the Public Beta 76 | - Create ChatKit with a name "SCRIMBA-CHATKIT-COURSE" 77 | - Then, we install [@pusher/chatkit](https://www.npmjs.com/package/@pusher/chatkit) in the terminal 78 | ``` 79 | yarn add @pusher/chatkit 80 | ``` 81 | - We import chatkit into app.js 82 | ``` 83 | import Chatkit from '@pusher/chatkit' 84 | ``` 85 | - To hookup a React.Component with an API by using lifecycle method, called componentDidMount(), we create chatManager 86 | ``` 87 | componentDidMount() { 88 | const chatManager = new Chatkit.ChatManager({}) 89 | } 90 | ``` 91 | >![screen shot 2018-09-06 at 3 24 03 pm](https://user-images.githubusercontent.com/36870689/45185892-f70aa880-b1e8-11e8-84d8-772c10d2b3d6.png) 92 | >![screen shot 2018-09-06 at 3 26 05 pm](https://user-images.githubusercontent.com/36870689/45185985-4224bb80-b1e9-11e8-8f9b-5bbf5fa4b657.png) 93 | > After you create a thing with ChatKit, they will provide Instance Locator and Token, which we will need to copy those information and pass them into config.js. 94 | - My config.js looks like this after I copy the Token and Instance Locator info that they provided from their ChatKit's website 95 | ``` 96 | const tokenUrl = "https://us1.pusherplatform.io/services/chatkit_token_provider/v1/4ed9ba1e-a897-48dc-ba84-f57c5668e0fa/token"; 97 | const instanceLocator = "v1:us1:4ed9ba1e-a897-48dc-ba84-f57c5668e0fa"; 98 | 99 | exports.tokenUrl = tokenUrl; 100 | exports.instanceLocator = instanceLocator; 101 | ``` 102 | - Obviously, after getting the config.js ready, we need to import the config.js into app.js 103 | ``` 104 | import { tokenUrl, instanceLocator } from './config' 105 | ``` 106 | ``` 107 | componentDidMount() { 108 | const chatManager = new Chatkit.ChatManager({ 109 | instanceLocator, 110 | userId: 'tienborland', 111 | tokenProvider: new Chatkit.TokenProvider({ 112 | url: tokenUrl 113 | }) 114 | }) 115 | 116 | chatManager.connect() 117 | .then(currentUser => { 118 | currentUser.subscribeToRoom({ 119 | roomId: 15580895, 120 | hooks: { 121 | onNewMessage: message => { 122 | console.log('message.text: ', message.text); 123 | } 124 | } 125 | }) 126 | }) 127 | } 128 | ``` 129 | - This is what is look like after I played around with create users and add message to the room, named Random, that I created 130 | >![screen shot 2018-09-06 at 3 47 56 pm](https://user-images.githubusercontent.com/36870689/45186926-51593880-b1ec-11e8-9ae2-688947d16eba.png) 131 | 132 | ## Step 4: State and Props 133 | - State is private for component (ex: it is only be state of the App component) 134 | - Prop is not private, is shared between components - and we cannot changed the props 135 | ``` 136 | constructor() { 137 | super() //when we call super() -> we call the constructor function in the React.Component class 138 | this.state = { 139 | messages: [] 140 | } 141 | } 142 | ``` 143 | - Now we can add this.setState inside the function .then 144 | ``` 145 | this.setState({ 146 | messages: [...this.state.messages, message] 147 | }) 148 | ``` 149 | - Then we pass the prop -> because when we pass state from the top to render, it will turn into prop 150 | ``` 151 | 152 | ``` 153 | - The app.js will look like this: 154 | ``` 155 | class App extends React.Component { 156 | 157 | constructor() { 158 | super() 159 | this.state = { 160 | messages: [] 161 | } 162 | } 163 | 164 | componentDidMount() { 165 | const chatManager = new Chatkit.ChatManager({ 166 | instanceLocator, 167 | userId: 'perborgen', 168 | tokenProvider: new Chatkit.TokenProvider({ 169 | url: tokenUrl 170 | }) 171 | }) 172 | 173 | chatManager.connect() 174 | .then(currentUser => { 175 | currentUser.subscribeToRoom({ 176 | roomId: 9434230, 177 | hooks: { 178 | onNewMessage: message => { 179 | this.setState({ 180 | messages: [...this.state.messages, message] 181 | }) 182 | } 183 | } 184 | }) 185 | }) 186 | } 187 | 188 | render() { 189 | return ( 190 |
191 | 192 | 193 | 194 | 195 |
196 | ); 197 | } 198 | } 199 | ``` 200 | - And obviously, we need to pass the prop inside MessageList.js so that everytime the message changes, they will rerender the MessageList.js, so the MessageList.js will look like this (no longer passing the DUMMY_DATA) 201 | ``` 202 | class MessageList extends React.Component { 203 | render() { 204 | return ( 205 |
206 | {this.props.messages.map((message, index) => { 207 | return ( 208 |
209 |
{message.senderId}
210 |
{message.text}
211 |
212 | ) 213 | })} 214 |
215 | ) 216 | } 217 | } 218 | ``` 219 | - What we changed in the MessageList.js is DUMMY_DATA.map to this.props.messages.map 220 | ``` 221 | {DUMMY_DATA.map((message, index) => { 222 | return ( 223 |
//each child needs to have a unique "key" prop 224 |
{message.senderId}
225 |
{message.text}
226 |
227 | ) 228 | })} 229 | ``` 230 | 231 | ## Step 5: Create the Message component (Message.js) 232 | - Because we will need to move some contents from MessageList.js into Message.js. For example: 233 | ``` 234 |
235 |
{message.senderId}
236 |
{message.text}
237 |
238 | ``` 239 | - But first, don't forget to import Message.js into MessageList.js and render outto MessageList.js 240 | - So literally, we replace the whole div contents above with 241 | - So the MessageList.js will look like this: 242 | ``` 243 | import React from 'react' 244 | import Message from './Message' 245 | 246 | class MessageList extends React.Component { 247 | render() { 248 | return ( 249 |
250 | {this.props.messages.map((message, index) => { 251 | return ( 252 | 253 | ) 254 | })} 255 |
256 | ) 257 | } 258 | } 259 | 260 | export default MessageList 261 | ``` 262 | - So when we moved the div contents for message.senderID and message.text from MessageList.js over to Message.js, we will need to change to this.props.username (which is linked to MessageList.js) and this.props.text 263 | - And the Message.js will look like this: 264 | ``` 265 | import React from 'react' 266 | 267 | class Message extends React.Component { 268 | render() { 269 | return ( 270 |
271 |
{this.props.username}
272 |
{this.props.text}
273 |
274 | ) 275 | } 276 | } 277 | 278 | export default Message 279 | ``` 280 | - Check out this link to learn about [FUNCTIONAL COMPONENT](https://reactjs.org/docs/components-and-props.html) 281 | - We can also change the "Class Component" into "Functional Component" and the Message.js under Functional Component will look like this: 282 | ``` 283 | import React from 'react' 284 | 285 | function Message(props) { 286 | return ( 287 |
288 |
{props.username}
289 |
{props.text}
290 |
291 | ) 292 | } 293 | 294 | export default Message 295 | ``` 296 | - To change a Class Component into a Functional Component, we will no longer need "extends React.Component" and "render()". We also do not need the this. 297 | 298 | ## Step 6: SendMessageForm component (SendMessageForm.js) 299 | - The basic SendMessageForm.js should look like this at first: 300 | ``` 301 | import React from 'react' 302 | 303 | class SendMessageForm extends React.Component { 304 | render() { 305 | return ( 306 |
307 | 310 |
311 | ) 312 | } 313 | } 314 | 315 | export default SendMessageForm 316 | ``` 317 | - We will need to add onChange and value inside the input and onSubmit in the form 318 | ``` 319 | onChange={this.handleChange} 320 | value={this.state.message} 321 | ``` 322 | - After all, we will need to add handleChange and handleSubmit and binding them 323 | ``` 324 | import React from 'react' 325 | 326 | class SendMessageForm extends React.Component { 327 | 328 | constructor() { 329 | super() 330 | this.state = { 331 | message: '' 332 | } 333 | this.handleChange = this.handleChange.bind(this) 334 | this.handleSubmit = this.handleSubmit.bind(this) 335 | } 336 | 337 | handleChange(e) { 338 | this.setState({ 339 | message: e.target.value 340 | }) 341 | } 342 | 343 | handleSubmit(e) { 344 | e.preventDefault() 345 | console.log(this.state.message) 346 | /** send off the message */ 347 | } 348 | 349 | render() { 350 | return ( 351 |
354 | 359 |
360 | ) 361 | } 362 | } 363 | 364 | export default SendMessageForm 365 | ``` 366 | 367 | ## Step 7: Broadcasting Messages 368 | - As we noticed, in app.js, the interaction with ChatKit API - happens through this "currentUser" object - which we got access through the "componentDidMount" method and after we got connect with the "chatManager". 369 | - However, it is only available in the "currentUser" scope and that's not good => because we want it to be available in entire "componentDidMount" instance. 370 | ``` 371 | componentDidMount() { 372 | const chatManager = new Chatkit.ChatManager({ 373 | instanceLocator, 374 | userId: 'perborgen', 375 | tokenProvider: new Chatkit.TokenProvider({ 376 | url: tokenUrl 377 | }) 378 | }) 379 | 380 | chatManager.connect() 381 | .then(currentUser => { 382 | currentUser.subscribeToRoom({ 383 | roomId: 9434230, 384 | hooks: { 385 | onNewMessage: message => { 386 | this.setState({ 387 | messages: [...this.state.messages, message] 388 | }) 389 | } 390 | } 391 | }) 392 | }) 393 | } 394 | ``` 395 | - In the future, we will do the "currentUser.sendMessage" - in order to sendMessage. To do that, we need to type: (Simply, we hooke the currentUser with the component itself) 396 | ``` 397 | this.currentUser = currentUser 398 | ``` 399 | - After that we can create a new Method, called sendMessage(){}: (It's only possible when when we hooke the currentUser with itself component) 400 | ``` 401 | sendMessage(text) { 402 | this.currentUser.sendMessage({ 403 | text, 404 | roodId: 9434230 405 | }) 406 | } 407 | ``` 408 | - Obviously, we cannot forget to bind them under constructor 409 | ``` 410 | this.sendMessage = this.sendMessage.bind(this) 411 | ``` 412 | - Finally, we need to link the child component with its parents. sendMessageForm will get access through the sendMessage(text){} 413 | ``` 414 | 415 | ``` 416 | - In SendMessageForm.js, we ned to add under handleSubmit(e) 417 | ``` 418 | this.props.sendMessage(this.state.message) 419 | this.setState({ 420 | message: '' 421 | }) 422 | ``` 423 | - The SendMessageForm.js needs to look like this: 424 | ```import React from 'react' 425 | 426 | class SendMessageForm extends React.Component { 427 | 428 | constructor() { 429 | super() 430 | this.state = { 431 | message: '' 432 | } 433 | this.handleChange = this.handleChange.bind(this) 434 | this.handleSubmit = this.handleSubmit.bind(this) 435 | } 436 | 437 | handleChange(e) { 438 | this.setState({ 439 | message: e.target.value 440 | }) 441 | } 442 | 443 | handleSubmit(e) { 444 | e.preventDefault() 445 | this.props.sendMessage(this.state.message) 446 | this.setState({ 447 | message: '' 448 | }) 449 | } 450 | 451 | render() { 452 | return ( 453 |
456 | 461 |
462 | ) 463 | } 464 | } 465 | 466 | export default SendMessageForm 467 | ``` 468 | - And App.js needs to look like this: 469 | ``` 470 | class App extends React.Component { 471 | 472 | constructor() { 473 | super() 474 | this.state = { 475 | messages: [] 476 | } 477 | this.sendMessage = this.sendMessage.bind(this) 478 | } 479 | 480 | componentDidMount() { 481 | const chatManager = new Chatkit.ChatManager({ 482 | instanceLocator, 483 | userId: 'perborgen', 484 | tokenProvider: new Chatkit.TokenProvider({ 485 | url: tokenUrl 486 | }) 487 | }) 488 | 489 | chatManager.connect() 490 | .then(currentUser => { 491 | this.currentUser = currentUser 492 | this.currentUser.subscribeToRoom({ 493 | roomId: 9434230, 494 | hooks: { 495 | onNewMessage: message => { 496 | this.setState({ 497 | messages: [...this.state.messages, message] 498 | }) 499 | } 500 | } 501 | }) 502 | }) 503 | } 504 | 505 | sendMessage(text) { 506 | this.currentUser.sendMessage({ 507 | text, 508 | roomId: 9434230 509 | }) 510 | } 511 | 512 | render() { 513 | return ( 514 |
515 | 516 | 517 | 518 | 519 |
520 | ); 521 | } 522 | } 523 | ``` 524 | 525 | ## Step 8: RoomList Component (RoomList.js) 526 | ``` 527 | this.currentUser.getJoinableRooms() 528 | ``` 529 | - This will return a promise, all under chatManager.connect 530 | ``` 531 | .then(joinableRooms => { 532 | this.setState({ 533 | joinableRooms, 534 | joinedRooms: this.currentUser.rooms 535 | }) 536 | }) 537 | .catch(err => console.log('error on joinableRooms: ', errr)) 538 | ``` 539 | - We cannot forget to add joinableRooms and joinedRooms in constructor: 540 | ``` 541 | constructor() { 542 | super() 543 | this.state = { 544 | messages: [], 545 | joinableRooms: [], 546 | joinedRooms: [] 547 | } 548 | this.sendMessage = this.sendMessage.bind(this) 549 | } 550 | ``` 551 | - And edit RoomList under render 552 | ``` 553 | 554 | ``` 555 | - App.js will look like this now: 556 | ``` 557 | class App extends React.Component { 558 | 559 | constructor() { 560 | super() 561 | this.state = { 562 | messages: [], 563 | joinableRooms: [], 564 | joinedRooms: [] 565 | } 566 | this.sendMessage = this.sendMessage.bind(this) 567 | } 568 | 569 | componentDidMount() { 570 | const chatManager = new Chatkit.ChatManager({ 571 | instanceLocator, 572 | userId: 'perborgen', 573 | tokenProvider: new Chatkit.TokenProvider({ 574 | url: tokenUrl 575 | }) 576 | }) 577 | 578 | chatManager.connect() 579 | .then(currentUser => { 580 | this.currentUser = currentUser 581 | 582 | this.currentUser.getJoinableRooms() 583 | .then(joinableRooms => { 584 | this.setState({ 585 | joinableRooms, 586 | joinedRooms: this.currentUser.rooms 587 | }) 588 | }) 589 | .catch(err => console.log('error on joinableRooms: ', errr)) 590 | 591 | this.currentUser.subscribeToRoom({ 592 | roomId: 9434230, 593 | hooks: { 594 | onNewMessage: message => { 595 | this.setState({ 596 | messages: [...this.state.messages, message] 597 | }) 598 | } 599 | } 600 | }) 601 | }) 602 | .catch(err => console.log('error on connecting: ', errr)) 603 | 604 | } 605 | 606 | sendMessage(text) { 607 | this.currentUser.sendMessage({ 608 | text, 609 | roomId: 9434230 610 | }) 611 | } 612 | 613 | render() { 614 | return ( 615 |
616 | 617 | 618 | 619 | 620 |
621 | ); 622 | } 623 | } 624 | ``` 625 | - RoomList.js will look like this: 626 | ``` 627 | import React from 'react' 628 | 629 | class RoomList extends React.Component { 630 | render () { 631 | return ( 632 |
633 |
    634 |

    Your rooms:

    635 | {this.props.rooms.map(room => { 636 | return ( 637 |
  • 638 | # {room.name} 639 |
  • 640 | ) 641 | })} 642 |
643 |
644 | ) 645 | } 646 | } 647 | 648 | export default RoomList 649 | ``` 650 | > ![screen shot 2018-09-10 at 10 23 12 am](https://user-images.githubusercontent.com/36870689/45310489-9b3f6880-b4e3-11e8-9c71-076f90ed7eb9.png) 651 | 652 | ## Step 9: Subcribe to rooms 653 | - So in the past, we're still subscribe to a specific room (ex: roomId: 9434230) 654 | - To able to clikc on the room we want, we will create a subscribeToRoom and bind them with the constructor 655 | ``` 656 | subscribeToRoom() { 657 | this.currentUser.subscribeToRoom({ 658 | roomId: 9434230, 659 | hooks: { 660 | onNewMessage: message => { 661 | this.setState({ 662 | messages: [...this.state.messages, message] 663 | }) 664 | } 665 | } 666 | }) 667 | } 668 | ``` 669 | ``` 670 | this.subscribeToRoom = this.subscribeToRoom.bind(this) 671 | ``` 672 | - Also make the getRoom() by pulling this.currentUser.getJoinableRooms() into it 673 | ``` 674 | getRooms() { 675 | this.currentUser.getJoinableRooms() 676 | .then(joinableRooms => { 677 | this.setState({ 678 | joinableRooms, 679 | joinedRooms: this.currentUser.rooms 680 | }) 681 | }) 682 | .catch(err => console.log('error on joinableRooms: ', errr)) 683 | } 684 | ``` 685 | - Also, don't forget to bind the getRoom() with constructor 686 | ``` 687 | this.getRooms = this.getRooms.bind(this) 688 | ``` 689 | - The "componentDidMount" will be really organized after we pull subscribeToRoom() and getRooms() out as seperate and also changed the roomID inside "subscribeToRoom()". Plus, also delete this.subscribeToRoom() out of "chatManager.connect()" 690 | ``` 691 | class App extends React.Component { 692 | 693 | constructor() { 694 | super() 695 | this.state = { 696 | roomId: null, 697 | messages: [], 698 | joinableRooms: [], 699 | joinedRooms: [] 700 | } 701 | this.sendMessage = this.sendMessage.bind(this) 702 | this.subscribeToRoom = this.subscribeToRoom.bind(this) 703 | this.getRooms = this.getRooms.bind(this) 704 | } 705 | 706 | componentDidMount() { 707 | const chatManager = new Chatkit.ChatManager({ 708 | instanceLocator, 709 | userId: 'perborgen', 710 | tokenProvider: new Chatkit.TokenProvider({ 711 | url: tokenUrl 712 | }) 713 | }) 714 | 715 | chatManager.connect() 716 | .then(currentUser => { 717 | this.currentUser = currentUser 718 | this.getRooms() 719 | //this.subscribeToRoom() //delete this line 720 | }) 721 | .catch(err => console.log('error on connecting: ', errr)) 722 | } 723 | 724 | getRooms() { 725 | this.currentUser.getJoinableRooms() 726 | .then(joinableRooms => { 727 | this.setState({ 728 | joinableRooms, 729 | joinedRooms: this.currentUser.rooms 730 | }) 731 | }) 732 | .catch(err => console.log('error on joinableRooms: ', errr)) 733 | } 734 | 735 | subscribeToRoom(roomID) { 736 | this.setState({ messages: [] }) //added this line to clean up the state 737 | this.currentUser.subscribeToRoom({ 738 | roomId: roomID, 739 | hooks: { 740 | onNewMessage: message => { 741 | this.setState({ 742 | messages: [...this.state.messages, message] 743 | }) 744 | } 745 | } 746 | }) 747 | .then(room => { 748 | this.setState({ 749 | roomId: room.id 750 | }) 751 | this.getRooms() 752 | }) 753 | .catch(err => console.log('error on subscribing to room: ', err)) 754 | } 755 | 756 | sendMessage(text) { 757 | this.currentUser.sendMessage({ 758 | text, 759 | roomId: this.state.roomId 760 | }) 761 | } 762 | 763 | render() { 764 | return ( 765 |
766 | 769 | 770 | 771 | 772 |
773 | ); 774 | } 775 | } 776 | ``` 777 | - The code above did edit the RoomList under render by adding subscribeToRoom in it. 778 | - The RoomList.js will look like this after added the "onClick" with this.props.subscribeToRoom(room.id) 779 | ``` 780 | class RoomList extends React.Component { 781 | render () { 782 | return ( 783 |
784 | 798 |
799 | ) 800 | } 801 | } 802 | ``` -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "build-a-chat-app-with-reactjs-and-chatkit", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@pusher/chatkit": "^0.7.17", 7 | "react": "^16.5.0", 8 | "react-dom": "^16.5.0", 9 | "react-scripts": "1.1.5" 10 | }, 11 | "scripts": { 12 | "start": "react-scripts start", 13 | "build": "react-scripts build", 14 | "test": "react-scripts test --env=jsdom", 15 | "eject": "react-scripts eject" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nguy2819/Build-a-chatapp-with-Reactjs-and-Chatkit/0d980b231c7686634ac1da0cc9826c2424311036/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 22 | Build a Chat App with React.js 23 | 24 | 25 | 26 |
27 | 28 | 29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Chatkit from '@pusher/chatkit' 3 | import MessageList from './components/MessageList' 4 | import SendMessageForm from './components/SendMessageForm' 5 | import RoomList from './components/RoomList' 6 | import NewRoomForm from './components/NewRoomForm' 7 | 8 | import { tokenUrl, instanceLocator } from './config' 9 | 10 | class App extends React.Component { 11 | constructor() { 12 | super() 13 | this.state = { 14 | currentRoomId: null, 15 | joinableRooms: [], 16 | joinedRooms: [], 17 | messages: [] 18 | } 19 | this.subscribeToRoom = this.subscribeToRoom.bind(this) 20 | this.sendMessage = this.sendMessage.bind(this) 21 | this.subscribeToRoom = this.subscribeToRoom.bind(this) 22 | } 23 | 24 | componentDidMount () { 25 | const chatManager = new Chatkit.ChatManager({ 26 | instanceLocator: instanceLocator, 27 | userId: "tienborland", 28 | tokenProvider: new Chatkit.TokenProvider({ 29 | url: tokenUrl 30 | }) 31 | }) 32 | 33 | chatManager.connect() 34 | .then(currentUser => { 35 | this.currentUser = currentUser 36 | return this.currentUser.getJoinableRooms() 37 | .then(joinableRooms => { 38 | this.setState({ 39 | joinableRooms, 40 | joinedRooms: this.currentUser.rooms 41 | }) 42 | }) 43 | }) 44 | .catch(err => console.log('error connecting: ', err)) 45 | } 46 | 47 | sendMessage(text) { 48 | this.currentUser.sendMessage({ 49 | text, 50 | roomId: this.state.currentRoomId 51 | }) 52 | } 53 | 54 | createRoom(name) { 55 | this.currentUser.createRoom({ 56 | name 57 | }) 58 | .then(room => this.subscribeToRoom(room.id)) 59 | .catch(err => console.log(err)) 60 | } 61 | 62 | subscribeToRoom(roomId) { 63 | this.setState({ 64 | messages: [] 65 | }); 66 | this.currentUser.subscribeToRoom({ 67 | roomId: roomId, 68 | hooks: { 69 | onNewMessage: message => { 70 | this.setState({ 71 | messages: [...this.state.messages, message] 72 | }) 73 | } 74 | } 75 | }) 76 | .then(currentRoom => { 77 | this.setState({currentRoomId: currentRoom.id}) 78 | return this.currentUser.getJoinableRooms() 79 | .then(joinableRooms => { 80 | this.setState({ 81 | joinableRooms, 82 | joinedRooms: this.currentUser.rooms 83 | }) 84 | }) 85 | }) 86 | .catch(err => console.log('error on subscribing: ', err)) 87 | } 88 | 89 | render() { 90 | return ( 91 |
92 | 96 | 99 | 100 | 103 |
104 | ); 105 | } 106 | } 107 | 108 | export default App -------------------------------------------------------------------------------- /src/components/Message.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | class Message extends React.Component { 4 | render() { 5 | return ( 6 |
7 |
{this.props.user}
8 |
{this.props.text}
9 |
10 | ) 11 | } 12 | } 13 | 14 | export default Message 15 | 16 | //The component is stupid because only thing it does - are rendering the this.props.user and this.props.text 17 | // Need to learn more about Functional Component because in case, the component does not have state or any life cycle component method like ComponentDidMount() 18 | // People nowsaday prefer FUNCTIONAL COMPONENT rather than CLASS COMPONENT 19 | //Check out this link to learn about FUNCTIONAL COMPONENT https://reactjs.org/docs/components-and-props.html 20 | 21 | //This is how you change the whole Class Component above into Functional Component 22 | 23 | // function Message(props) { 24 | // return ( 25 | //
26 | //
{props.username}
27 | //
{props.text}
28 | //
29 | // ) 30 | // } 31 | 32 | -------------------------------------------------------------------------------- /src/components/MessageList.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import Message from './Message' 4 | 5 | class MessageList extends React.Component { 6 | componentWillUpdate() { 7 | const node = ReactDOM.findDOMNode(this); 8 | this.shouldScrollBottom = node.scrollTop + node.clientHeight + 50 >= node.scrollHeight; 9 | } 10 | 11 | componentDidUpdate() { 12 | if (this.shouldScrollBottom) { 13 | const node = ReactDOM.findDOMNode(this); 14 | node.scrollTop = node.scrollHeight 15 | } 16 | } 17 | 18 | render() { 19 | if (!this.props.currentRoomId) { 20 | return ( 21 |
22 |
23 | ← Join a room! 24 |
25 |
26 | ) 27 | } 28 | return ( 29 |
30 | {this.props.messages.map((message, index) => { 31 | return 32 | //Message here is the child of the MessageList 33 | })} 34 |
35 | ) 36 | } 37 | } 38 | 39 | export default MessageList -------------------------------------------------------------------------------- /src/components/NewRoomForm.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | class NewRoomForm extends React.Component { 4 | constructor () { 5 | super() 6 | this.state= { 7 | roomName: '' 8 | } 9 | this.handleChange = this.handleChange.bind(this); 10 | this.handleSubmit = this.handleSubmit.bind(this); 11 | } 12 | 13 | handleChange(e) { 14 | this.setState({ 15 | roomName: e.target.value 16 | }) 17 | } 18 | 19 | handleSubmit (e) { 20 | e.preventDefault() 21 | this.props.onSubmit(this.state.roomName) 22 | this.setState({ 23 | roomName: '' 24 | }) 25 | } 26 | 27 | render () { 28 | return ( 29 |
30 |
31 | 37 | 38 |
39 |
40 | ) 41 | } 42 | } 43 | 44 | export default NewRoomForm -------------------------------------------------------------------------------- /src/components/RoomList.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | class RoomList extends React.Component { 4 | render () { 5 | const orderedRooms = [...this.props.rooms].sort((a, b) => a.id > b.id); 6 | return ( 7 |
8 |

Your rooms:

9 | 21 |
22 | ) 23 | } 24 | } 25 | 26 | export default RoomList -------------------------------------------------------------------------------- /src/components/SendMessageForm.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | class SendMessageForm extends React.Component { 4 | constructor() { 5 | super(); 6 | this.state = { 7 | message: '' 8 | }; 9 | this.handleChange = this.handleChange.bind(this); 10 | this.handleSubmit = this.handleSubmit.bind(this); 11 | } 12 | 13 | handleChange(e) { 14 | this.setState({ 15 | message: e.target.value 16 | }); 17 | } 18 | 19 | handleSubmit(e) { 20 | e.preventDefault(); 21 | this.props.sendMessage(this.state.message); 22 | this.setState({ 23 | message: '' 24 | }) 25 | } 26 | 27 | render() { 28 | return ( 29 |
30 | 36 |
37 | ) 38 | } 39 | } 40 | 41 | export default SendMessageForm -------------------------------------------------------------------------------- /src/config.js: -------------------------------------------------------------------------------- 1 | const tokenUrl = "https://us1.pusherplatform.io/services/chatkit_token_provider/v1/4ed9ba1e-a897-48dc-ba84-f57c5668e0fa/token"; 2 | const instanceLocator = "v1:us1:4ed9ba1e-a897-48dc-ba84-f57c5668e0fa"; 3 | 4 | exports.tokenUrl = tokenUrl; 5 | exports.instanceLocator = instanceLocator; 6 | 7 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | 4 | import App from './App'; 5 | 6 | ReactDOM.render(, document.getElementById('root')); -------------------------------------------------------------------------------- /src/style.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --main-color: #5ea3d0; 3 | --secondary-color: white; 4 | --main-text-color: #3e5869; 5 | --secondary-text-color: #b0c7d6; 6 | --send-message-form: #F5F5F5; 7 | } 8 | 9 | html, body { 10 | height: 100%; 11 | margin: 0; 12 | padding: 0; 13 | font-family: system-ui; 14 | font-weight: 200; 15 | color: #3e5869; 16 | } 17 | 18 | #root { 19 | height: 100%; 20 | } 21 | 22 | .app { 23 | display: grid; 24 | height: 100%; 25 | grid-template-columns: repeat(6, 1fr); 26 | grid-template-rows: 1fr 1fr 1fr 1fr 1fr 60px; 27 | grid-template-areas: 28 | "r m m m m m" 29 | "r m m m m m" 30 | "r m m m m m" 31 | "r m m m m m" 32 | "r m m m m m" 33 | "n f f f f f"; 34 | } 35 | 36 | .new-room-form { 37 | grid-area: n; 38 | } 39 | 40 | .rooms-list { 41 | grid-area: r; 42 | } 43 | 44 | .message-list { 45 | grid-area: m; 46 | } 47 | 48 | .send-message-form { 49 | grid-area: f; 50 | } 51 | 52 | /* REST OF CSS */ 53 | .rooms-list { 54 | box-sizing: border-box; 55 | padding: 10px; 56 | background-color: var(--main-color); 57 | overflow: scroll; 58 | height: 100%; 59 | } 60 | 61 | .rooms-list > ul { 62 | list-style-type: none; 63 | padding: 0; 64 | overflow: scoll; 65 | } 66 | 67 | .rooms-list li { 68 | margin: 10px 0; 69 | } 70 | 71 | .rooms-list > h3 { 72 | margin: 5px 0; 73 | color: var(--secondary-color); 74 | } 75 | 76 | .rooms-list .room a { 77 | color: var(--secondary-text-color); 78 | font-weight: 600; 79 | text-decoration: none; 80 | 81 | } 82 | 83 | .rooms-list .room.active a { 84 | color: var(--secondary-color); 85 | } 86 | 87 | .new-room-form { 88 | padding: 0 5px; 89 | background: var(--secondary-color); 90 | color: var(--main-text-color); 91 | } 92 | 93 | .new-room-form form { 94 | height: 100%; 95 | display: flex; 96 | justify-content: space-between; 97 | align-items: center; 98 | } 99 | 100 | .new-room-form input { 101 | width: 135px; 102 | background: var(--secondary-color); 103 | } 104 | 105 | .new-room-form button { 106 | background: var(--secondary-color); 107 | color: var(--main-text-color); 108 | border: 0; 109 | } 110 | 111 | .new-room-form input::placeholder { 112 | color: var(--main-text-color); 113 | font-weight: 200; 114 | } 115 | 116 | .new-room-form input:focus { 117 | outline-width: 0; 118 | } 119 | 120 | .new-room-form input { 121 | border: 0; 122 | } 123 | 124 | .new-room-form button { 125 | border: 0; 126 | } 127 | 128 | .message { 129 | margin: 15px 0; 130 | } 131 | 132 | .message .message-username { 133 | font-size: 11px; 134 | color: var(--main-text-color); 135 | opacity: 0.9; 136 | margin-bottom: 6px; 137 | } 138 | .message .message-text { 139 | background: var(--main-color); 140 | color: var(--secondary-color); 141 | display: inline; 142 | padding: 4px 8px; 143 | border-radius: 8px; 144 | } 145 | 146 | .message-list { 147 | box-sizing: border-box; 148 | padding-left: 6px; 149 | width: 100%; 150 | height: 100%; 151 | overflow: scroll; 152 | background: var(--secondary-color); 153 | } 154 | 155 | .message-list .join-room { 156 | display: flex; 157 | justify-content: center; 158 | align-items: center; 159 | height: 100%; 160 | font-size: 34px; 161 | font-weight: 300; 162 | } 163 | 164 | .send-message-form { 165 | background: var(--send-message-form); 166 | display: flex; 167 | } 168 | 169 | .send-message-form input { 170 | width: 100%; 171 | padding: 15px 10px; 172 | margin: 0; 173 | border-style: none; 174 | background: var(--send-message-form); 175 | font-weight: 200; 176 | 177 | } 178 | 179 | .send-message-form input:focus { 180 | outline-width: 0; 181 | } 182 | 183 | .send-message-form input::placeholder { 184 | color: var(--main-text-color); 185 | } 186 | 187 | .help-text { 188 | position: absolute; 189 | top: 10px; 190 | } 191 | --------------------------------------------------------------------------------