├── .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 | >
92 | >
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 | >
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 |
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 |
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 |
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 |
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 |
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 |
644 | )
645 | }
646 | }
647 |
648 | export default RoomList
649 | ```
650 | > 
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 |
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 | //