Lesson 19

Sending messages


Now that we can properly connect to our rooms, we can focus on the fun part – sending and receiving messages. Here's how it's going to work:

  1. The user types a message in the input and clicks "send"

  2. The message is pushed to the room's channel

  3. Phoenix receives the message, adds it to the database, and broadcasts its ID to all the members of that room

  4. The room members receive the new message ID and use it to fetch the message object from the API

  5. The message's full JSON is returned and the users can add it to their application's store with Redux.

As you can see, we have quite some work to do, so let's just take it one step at a time.

Creating a message

The first thing we want to do is to be able to read what message the user has written in the input tag. For that, we'll add some state in the ChatContainer component that gets updated whenever the user types:


Next, we'll also handle the click event on the button, so that we can read the state of the draft, log it, and reset it:


If you type a message and click "Send", you should now see it being logged in your console.

So far so good. Now, in order to send this message out to the room's channel, we need to retrieve the channel object for the room (stored in the Redux store), and use .push, just like we did previously when we "shouted" in the "lobby".

We start by passing down room as a prop (because we need both the room's id and its channel), near the end of the file:


Then we push the message out to the channel – under the identifier "message:new" – in our sendMessage method:


If we try to send a message again, it might seem as if nothing has changed. But that's only because we're not handling this incoming message on the Phoenix server! Let's bring back a handle_in/2-function that prints the message and the room's ID, then returns an :ok message:


Now you should see the message in your Phoenix console:

Cool! Let's keep following our checklist. The next thing we want to do is to add this message to our database, and then broadcast the ID to all the users of the room!


So how do we make sure that the client can receive this JSON message? For that, we head back to our menu-container.js-file, where we previously connected to all the room channels, and we'll create a new listenToNewMessages-method, which will be called for each of these channels:


You should now be able to send messages from one user, and see its ID appear in the log of the other user almost instantly! Cool!

Fetching the message with AJAX

Instead of just logging the message, we will now create a getNewMessage method, where we fetch a message's JSON given its ID:


The only problem now is that the API endpoint /messages/{message_id} doesn't exist, so we need to create that as well. You should know the drill by now when it comes to creating routes! We start with the router file:


Since the MessageController doesn't exist yet, we need to create the file and add a show/2-function. In the function, we need to fetch the message, with its room, and make sure that the user is allowed to see the requested message (we don't want users to be able to fetch messages from a room unless they're a member).

To do this, we'll create a new get_message/1-function in our "chat" context, and then we'll use our existing Chat.room_has_user?/2-function in the controller:


In order to handle the :not_allowed response, we need to add a new pattern matched function in the fallback controller:


Finally, we need a render/3 function in our MessageView so that we can show our JSON. Luckily, most of the work has already been done in that file, thanks to the message_json/2-function that we created earlier:


Now try to send messages again, and you'll see the full message JSON being logged instead of just the ID!


Adding the message with Redux

We're almost there! The final step is to make the message appear in the UI using Redux. For this, need a new action called "ADD_MESSAGE". We can start by defining it in the actions.js-file. For this action, we're going to need the message object and the room ID in order to know how to manipulate the current state tree:


We can then import the action into menu-container.js and map it to a prop:


Finally, we add a reducer so that Redux knows how to change the state given the action it receives:


And that's it! Now try sending messages again.

Some last UI fixes

We've now implemented all the core functionality of our web app! Well done! There's only a few fixes to be made before we can move on. First, we should scroll down automatically whenever a new message is posted, otherwise we risk missing it, as you can see in the video below.

We'll fix this by creating a scrollToBottom function in chat-container.js, outside of the component class:


The idea here is that we will call this function to scroll down whenever there's a new message or whenever the user selects a new room.

To look for these changes, we can use React's componentDidUpdate-hook. There, we can compare our current props with the props we had before the last re-render. If the room prop's id has changed for example, it means the user just selected a new room, (and that the window should scroll)! Likewise, if the messages prop's length changes, that means a new message was just posted (and the window should scroll)!

Here's how we code this in the componentDidUpdate hook:


Now, that's better! Finally, it's also a bit tedious to have to click the "Send"-button with the cursor every time we want to send a message. We should be able to just press "enter"!

Fortunately, that's very easy, we just need to handle the onKeyPress event on our input tag and trigger the sendMessage method whenever "enter" is pressed:


Voila! Your app is now basically ready! However, that doesn't mean that there isn't more to learn. In the next lesson, we're going to go through automatic testing in Elixir!



Be the first to comment!