4.11 Creating the Follow Button
In this lesson, we're creating a simple component that just displays a button: the button that will allow users to follow and unfollow one another.
1.Getting Started2 lessons, 03:51
2.Project Setup3 lessons, 20:58
3.Server-Side Code6 lessons, 53:00
4.The Client Side16 lessons, 2:43:53
5.Conclusion1 lesson, 00:29
4.11 Creating the Follow Button
In our previous lesson, we created our user list here, which displays a list of all the users that the currently logged in user could follow or unfollow. Now, for following and unfollowing, we just put in the text Follow Buttons. However, in place of this text, we're going to put in an actual Follow button. Now this button is going to have a little bit of complex behavior. So we're gonna create the button as its own component first. So let's go ahead and create a new component. In src/components, we'll create something called the FollowButton. So FollowButton.js. Now we know that our follow button will need to actually perform some actions, right? Both following and unfollowing. So let's pull in our actions object as well which is ../actions. And the last thing we'll need is the user's store. And this is because the follow button needs to be kept updated with the user's current status. Basically who the user is currently following. So we'll say, UserStore =, and we'll require ../stores/users. All right. And let's create our follow button component. As we've done before, and you're very familiar with this now, we'll say module.exports. And then we'll say React.createClass. Now we'll start with our getInitialState function. And the state that the follow button needs to know, is who is the current user currently following. So let's create a currentlyFollowing array here, and this is going to be UserStore.currentUser, and we'll say, .following. This is an array that we created on our user, and it will keep track of the ID's of the other users that this user is currently following. Now because we need to get something from a store, we probably want to update this button whenever that store changes, right. So if we look at UserList here, we can actually just copy the ComponentDidMount, the ComponentWillMount, and onChange functions. Once again, we well just copy those and paste them into our class, and this is actually something we might want to think about for later on. We're using very similar functions in a lot of places, so maybe we should think about abstracting that. We'll come back to that idea, but for now, let's go ahead and create our render function. Now in the action of following another user, there are two users involved. There's the current user, who's data we've already pulled into this class, but then there's also the user that we want to follow, and that user's ID will be passed to the button as a property, user ID. Now, the first thing we want to do inside the render function, is compare these two IDs. If they're the same ID, that means that the user is trying to follow themselves. And we don't want to allow that. In fact, we kind of do allow that by default, but we don't want to un-allow that. So, we want to compare those two IDs, which means we actually need the current user's ID. So, up here in get initial state, let's actually add this. We'll say id equals UserStore.currentUser.id. I guess we could just turn this into a user object, but this will be fine. So the first thing we can do here, is say if this.state.id t=== this.props.userID then let's just return a span. And in this span, we'll say This is you! And we won't even return a button, so there's no chance that they can change the status of their own, following themselves. After this we want to determine if they are already following this user or not. Because if they're not following them, then this will be a follow button. However if they are following them, then this is going to be an unfollow button. And obviously there are two things that we need to set, based on whether they are following or not following that user. We want to set the text of the button, of course, to follow or unfollow. And we also want to set the action, whether we're doing unfollowing or following. So we can say if this.state.currentlyFollowing.indexOf and we will pass this.props.userId is greater than minus 1. That would mean that the user id is inside of the currentlyFollowing array. So if that's the case, we'll set text equal to Unfollow and we will set the action = to this.unfollow. Now that means we are going to need to create a method called unfollow. So let me go ahead and do that now. We'll say unfollow and this is going to be a very simple function. And of course, this means we're also going to have a follow function right? Unfollow and follow. Excellent. So if we are following user, well, then we want to unfollow them. Otherwise, we can set the text equal to follow and we can set the action equal to this.follow. Perfect. Finally, we'll just return a button. We need to give it onClick handler of whatever action we determined, and then inside this button, we can just set the text. There we go. Now, for these functions, it's actually very simple. We can say, actions, because remember, we pulled in our actions up at the top here? So, we can say actions.unfollow, and we'll pass at this.props.userId. And I'm just gonna copy that line, because the follow version is very similar it's just actions.follow and we pass this.props.userId. There we go. So this is our entire follow button. It looked pretty big for a button, but of course this behavior will be very useful. Now these actions will occur in our system when the button is clicked. However we don't have anything that is watching for those actions, so let's move to our api.js file, and let's register with the dispatcher for those actions. We already have our dispatcher.register function at the bottom of the API file. Right now we're only looking for the case of a chirp, however, we also want to look for case constants.FOLLOW. And when that's the case we'll do API.follow. And we'll pass it action.data which will be that user ID, and then we'll break, and then we'll also have a case for constants.UNFOLLOW. And in those cases we'll do API.unfollow, and again, action.data. All right, so let's come back up to our API here, and let's add a follow and unfollow method. So we'll have follow here. And this is going to take an id. And what we'll do is we'll call post, and we'll pass it /api/follow/ + id. And this will go to the server and say that the currently logged in user wants to follow the user with that id. And so then we can have a response, will say then and then we'll call actions.followed and again we'll do the bind to the actions object. So when our follow comes back and says that it's happened successfully on the server. We'll let the system know that the user was followed. Now, I can copy this, once again, and make a few changes. We'll call this unfollow and the route will be /api/unfollow and the action we wanna call is actions.unfollowed. Excellent. Now, we have to keep following this request through, because although we're posting to the server here, our server isn't currently set up to accept those calls. So, at the bottom of login.js, let's add these two last routes. So we'll have router.post. And we expect API/follow for starters. Slash token for the user ID there. And for starters, we'll convert that ID to a number, so we'll say parseint and we'll pass a req.params.id. And then we need to check to see if this is inside the current users following array. Now you might recall, because of Passport we can get the current user with req.user. So we can say req.user.following.indexOf id. And if they're not following the user it would be less than zero. So let's convert this to an if statement. And if they're not following the user, then we can do request.user.following.push, and we'll push that id into the users following array. Next of course we need to update the user. So we can do that through the users.update call users, remember, is our collection. And the update method takes the users id as the first parameter, so we can say request.user.cid. And the second parameter is the actual updated object. So we'll say request.user, which we just updated by pushing into its array. Finally, after that happens, whether the user has been updated or not, we will do response.Json and we will send request.user back. Actually, before we do that though. We should wrap that in a call to make user safe, so that we do not send the user's password or any other private information. Excellent. Now we can do something very similar for unfollow, as you might expect, so I'm just gonna copy this whole route and paste it underneath. Let's see, what do we need to change? So we wanna change the actual route to be unfollow, api/unfollow/id. And then our if statement should check to see that it is in the array, instead of that it's not. So we can switch this by saying it should be greater than -1. And actually, if it is in the array, we're gonna need this index again, so I'm just going to change that into a regular variable here. We'll say variable pos for position. And so we'll say if the position is greater than zero, then, instead of pushing into the array, we'll do request.users.following.splice, which is an in place array action. And we're gonna say, start at the position and move one unit. So what this will do is it will actually return the one item in the array that it cuts out, however we're not interested in saving that. But the benefit here is that splice will actually change the underlying array, req.user.following, so that it will no longer have that item that we pulled out. So then, of course, we'll update the user, and send that back to the server. Now we'll come back to the terminal and I will restart the server, because we've made changes to the server code. And if we come back to the user list file we can actually use our brand new follow button. So let's pull it in. We'll say FollowButton. And we can require the FollowButton js file. And down here where we said follow buttons. Let's change this and we will add the FollowButton in. And we expect the user id to be equal to the user.cid. Right? Because that is the user, who we are currently displaying with this box. So that should be all we need to do. If we come back to our application, of course, we'll have to log in again. However, after we do that, if we navigate to the user's page, you can see that we now have these follow buttons showing up. And if I If I go ahead and click follow, notice what's happening down in the console. We have a follow action being sent to the server. And then you can see we have a followed action coming back from the server. And better yet, notice that the button now says unfollow. Even better then that, if I refresh the page, you can see that this data was actually saved to the server, because now the button says unfollow. And I can follow and unfollow and you can see that the actions are continually happening as I click the button. Now that we can actually follow users, it would make sense, if on our homepage we would only see Chirps from users who we actually follow. Right now it just displays a list of all the Chirps. So let me go ahead and if we go to s/logout. And let's login as one of the other users, say Molly. Notice that right now both of those tweets are showing up in her timeline, because that's what we've done by default. However if we go to /users we can see that she actually isn't following anyone. So let's go ahead and filter this list of tweets on the home page. The place to do this would be in our ChirpStore. We can create a new method called timeline. And this will only show the tweets of the currently logged in user and anyone they're following. So let's start by getting an array of ids. We'll create an array here, and the first id that we want to put in, is the current user's id. We can get that from the UserStore, so let's pull in the user store, and we can require users. We don't need to use the stores part of the path, because we're actually already in that directory, when we're inside the chirps.js store file. So we can say UserStore.currentUser.cid and then lets concatenate this array with UserStore.currentUser.following. Then, what we want to do is return this._data, you might recall that's where we're storing our chirps, this._data. And then let's do filter here. And let's loop over all these chirps. And then what we'll do, is we'll only return where ids.indexOf(chirp.cid) is greater than -1. So only the chirps whose id'ss are in this array will be allowed through. So since the only users in this array are the logged in user and anyone they're following, this timeline function will return the right set of chirps. Now, let's open our home.js function. And here in getInitialState, instead of doing ChirpStore.all, we can do ChirpStore.timeline. And that should do it. If we come back to the browser and refresh the page, you can see that now no chirps show up in the timeline. If our user makes a chirp, their own chirp shows up, however, since they're not following anyone else, nobody else's chirps show up. However, let's go to /users, and let's chose to follow somebody else. Now, if we go back to the homepage, you can see that only one of our chirps show up. That's a little bit strange, however, I think I know what went wrong. If we come back to the chirps.js file, you can see that we compared the user ids to the chirp's ids. Instead we should have compared the user ids to the user id property on that chirp. That way, we're not comparing it to the id of that chirp, but the id of the user who made the chirp. Okay, that was an easy fix. So if I refresh the page, now you can see that our chirps are showing up. However, you may notice that these chirps are not showing up in the right order. We'll fix that in the next lesson.