4.10 Creating the User List Component
Now that we have a store of users, let's display them in a list.
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.10 Creating the User List Component
In this lesson we're going to create a view that displays a list of users. This is the view that our currently logged in user will use to choose different users to view or maybe to follow or unfollow users. So, we're going to start here in our main.js file. We're gonna start by creating a new route. And for starters, I'm just going to duplicate the home route here. And the first thing I'm going to do is change the name from home to users. So this is going to be called the users route. Now, I can also remove this path property entirely. I don't even have to change it to something else. The reason is, for this route, the path and the name are the same. So this way, we're telling the router that when the route is /users, we should display this handler. Now currently the handler here is set to Home. Instead, let's set it to components/UserList. So that means we have to create this UserList component. So let's go ahead and do that. In the components directory, I will create UserList.js. And we'll start by pulling in the obvious things. We're definitely going to need react, right? And we're obviously going to need the UserStore because we're working with a list of users. So we can get this from stores/users. Now we know that eventually we're going to have Follow and Unfollow buttons here, which means that we're going to want to put actions into our system, so we should pull in our actions file as well. And let's see, we also know that we're going to want to have links to individual users' profile pages, so we should pull in the react-router link component, right? So that's react-router and then we have .Link. All right, so that's good for starters. Let's go ahead and create our user list module. And of course, as we've been doing through many of our modules here, we'll go ahead and export it right away here. And this is going to be a react class. So we'll do React.createClass, and we're going to have to start by getting some initial state here. So we'll create a getInitialState function. And we really want two things in this object that we're going to return from getInitialState. The first, of course, is a list of users. So we're gonna say UserStore.all. Secondly, we also want the currently logged in user. So let's just create a user property to refer to the currently logged in user. And we know that this is UserStore.currentUser. Now right away, we can know that since this is what we could call a top level view, that is, this view will be rendered by our router itself, that we're probably going to want to update our state whenever the UserStore changes either the list of users or if it changes the current user in some way. For example, when we follow or unfollow a user. In either of those cases, we're gonna want to update the UI that's currently on the screen. Now we saw in our home component that the way to do this is by having a change listener that will update the state of the component whenever the store changes. So from home.js, I'm going to copy the componentDidMount, componentWillUnmount, and onChange functions. And I'm just gonna paste those in here to our user list. Now these were for the ChirpStore so I'm going to have to change these to call UserStore instead of ChirpStore. But other than that these should work just fine. We can even use onChange without changing it at all because again we just want to set the state object which we're getting from the getInitialState function so we can just call that another time. So with that in place, we can go ahead and write our render function. Now, similar to our ChirpList view, the UserList view is going to loop over a set of objects to render them in the UI. However, we don't want our list to include all of the users. We want it to include all the users, minus the currently logged in user. Right? We don't need to show them themselves in that list. So, let's have our list of items here. And we can say, this.state.users. And we'll start by doing a filter here. So loop over each user individually, and we can return wherever this.state.user which is the currently logged in user. Remember we set that in our state. So this.state.user.cid. Wherever that does not equal user.cid. So we only return users whose ids do not match the id of the currently logged in user. Now, this inside our function here will not be bound to our class, so we'll have to do that manually. But now items here is an array of the users that we do want to include in our list. All right, so now let's map this array into a set of list item elements. In fact, we can do this very simply if we just put our map here right in the end of our filter function here. So we can just return a list item. And let's make this really simple for now. We'll do something a little more complex in a minute here, but let's just say user.username and let's just throw an @ sign at the beginning of this. So at the bottom here we can just return an unordered list and inside of this we can put our items. All right, so let's see here. I think this looks good. Oh, one other thing. We need to have the double dot at the beginning of stores/users there, and also on actions there, because both of those are coming from the directory above our components directory. However, that looks good. We should be able to see our list of users. So if we come back to our browser and browse to localhost:3000/users, you can see that right now we have a list of two users. Now I created these users before the lesson. So we are currently logged in as the Andrew user. However you can see that we have user accounts for John and Molly. Let me go ahead and log out by going to the log out route. And now, if I log in as Molly, and go to the /users route, you can see that Andrew and John show up as the two users. So you can see that we're successfully filtering out the currently logged in user. Now, of course, we don't just want to display our users as a name like this. In fact, what we do want is something a little bit more like what we're showing here for our chirps. We'd like to have their name, their avatar, in a nice little box here. Maybe, instead of having the text of a chirp here, We can have a follow and unfollow buttons, or we could also have a link to their profile page. This structure that we've created would work very well on our user's list page. So let's see if we can generalize our Chirp box components to, just kind of be a box, that could be either a Chirp box or a user box. So, I'm going to open up our ChirpBox here, and right now it looks like our ChirpBox expects a single chirp as its only properties. However, let's think about the pieces of information that our box displays as a chirp. Well, we know it displays a user's full name. It displays their username as well. It displays their avatar. It also displays the text of the chirp, and it displays the timestamp for how long its been since that chirp was made. Now if we wanted to turn this into a user box instead, we would still show the full name, user name, and avatar instead of the text here. We would want to show some buttons and instead of the time stamp we would just have nothing at all. So this looks like something we could definitely generalize here. Now within our chirp box here the only property that it expects to receive right now is the chirp itself. However if we look at these properties here we kind of have two sets of them. First of all we have full name, user name, and avatar. All of these could be combined into a single property that is the user, right? And our ChirpBox or our more general box could get those properties off of the user element. In fact, it would also use some properties that we're not displaying, such as the user's email to get their avatar and of course their ID to create a link to their profile page. This time stamp here could be another property because it's something we want on one box and not on another. So it could be just kind of like an optional property if we include it in the properties of our box then the box will display it. And if we don't, then it won't display it. And finally, text versus buttons here. Well, we can create a react class that actually accepts child elements. And so we can either have the text as one of the child elements, or we could make those buttons the child elements. So our box doesn't really have to think about that at all. So let's see if we can generalize this ChirpBox here. In the first line in our render function, we're shortening up our Chirp variable here. So let's change the property name that we're expecting to be user. And right now the variable is c. I'm going to use my text editor's find and replace to change that to user in all locations. Then we also have a timestamp variable here. So let's use a expression. We can say this.props.timestamp. And what we'll do if there is no timestamp property, we'll just return an empty string. Otherwise, let's have a space and then plus. And I will just paste something in here. We have string.fromCharCode 8226. This will just be a vertically centered dot, which will act as a separator between what comes before the timestamp and the timestamp itself. What comes before the timestamp is, of course, our user name. So we'll have that. Then we will add another space after it, and finally, we'll have this.props.timestamp itself. Right, so let me move this onto multiple lines and hopefully that makes it a little more readable. So basically, if we have a time stamp we'll concatonate a bunch of things to make a nice string, displaying our time stamp, otherwise will be an empty string. All right, so now with those things in place most of this should already work. We first have our link here, that goes to the individual user page. And we have our avatar showing. We have user.fullname. Here's our timestamp span. Right now we have the user name and then we have where we're using moment here. Now because we're kind of abstracting the timestamp to be created outside of this box, we'll have to require moment in our ChirpList component, and actually create this timestamp there. But that's okay, I'm just gonna change this so that we just put our timestamp in right here. And then the last thing we have, we come down to the bottom where we have user.text. And like I said, we're going to actually allow this element that we're creating, our ChirpBox element, to have child elements. So we can change this from user.text to be this.props.children. And we don't actually have to create a children property on this element, we just put these children inside of the element and they will be converted to that property for us. All right, so the first thing we should do is make sure this is still working in our chirplist component. In fact, if we go back to the browser right now and refresh the page you'll probably see, yeah, it breaks. Because as you can see we have cannot read property user ID of undefined. So we have to go back to our chirp list component here, and right where we're creating ChirpBox, let's actually change it's name to just box. I'm not gonna change the file name just yet, but I will change the module name. So we say the box, and we're not expecting to ever receive a chirp, we're expecting to receive a user. And which in this case we'll just keep as the chirp because it has the user properties on it. We may change that later. Then we want to have some content inside this box element. And this is going to be Chirp.text, right? And then we need to add a time stamp property here so let's say timestamp equals and we're going to have moment and then we're going to wrap that in chirp.dollarsigncreated And then we're gonna say .fromNow, right. And I really hate wrapping lines so let's actually just move each one of these properties onto their own line. There we go. That looks a little bit better, and finally we do have to require a moment. So we'll say var moment equals require moment. And now I think this should work. So, if we come back to the browser and refresh our homepage here, you can see that our chirp box has now been turned into a general box and it still works very nicely for our individual chirps. So now we can use this in our user list file to display individual users. So first let's go ahead and pull this in. So we'll say var Box equals require. Well, it's still called ChirpBox, so we'll just leave that as the file name for now, but that's okay. And now in here, instead of creating a list item, we will create a box, and we have to give it a user property. Now we also have to give it a key property, remember, since we're looping over it. So that will be user.cid. We will not give it a timestamp property, which will not be a problem. And in here we're going to have our follow buttons. So I'll just put the text Follow Buttons in there for now. And let's see if this works. So I'll come back to our browser and head over to /users. And you can see that we do have an error. So we're missing the id parameter for the path /user/id. So that might seem a little bit vague. However, if we come back to our actual ChirpBox file. I can tell you that the error is caused by our link component here. The problem here is that we're asking it to go to the user route, and the user route requires an id parameter. And we're setting that to user.UserID. And that works from the chirp list when our user object here is actually a chirp. However it doesn't work when we're using it from the user list component because in that case the id is cid and not userid. So I think the best way to solve this problem is to not cheat and use a chirp object instead of a user object, but to actually pass an actual user object even from the chirp list. And we're actually set up to do this now because we have a user store, so lets require our user store and instead of passing this chirp here as a user, we can pass UserStore.get and we're gonna pass it chirp.userId. And this will get the actual user object from the store and make that our user. So now within ChirpBox, instead of expecting this property to be user ID, we can just expect it to be cID. So if we come back and refresh the users page notice that the contents is follow buttons right now. We'll get to that a bit later. And if we go back to our homepage, notice that we are having a problem. We can't read that CID property of undefined. And the key to understanding why this error is happening is to notice where this error is occurring. It's happening after our gotchirps action occurs but before the gotusers action occurs. The problem here is that at the time we're rendering our chirp, we're trying to get the user for that chirp, but the user hasn't been loaded from the server yet. And so that's why we can't actually get that user object. Now there's an easy but very bad practice way to fix this, and that is to fetch the users before we fetch the chirps. The reason this works in our situation is because the server is running on this local host and so we can be fairly certain that everything's going to come back nice and quickly, and that the users are probably going to be returned before the chirps are. If we try that, and refresh the page, notice that everything is working just fine. However, this is a bad practice. We should not expect that our requests will come back in a specific order. So how do we solve this problem in a good way? Well, what I think we're going to is within in our Chirp store here, we're going to go back to sending our chirp through as the actual user. This is what we wanted to avoid at first but we are using a document based database system, so there isn't really a reason not to include some user information in our chirp here. And so the chirp can double as a user in this case, because we already have that information so there's no need to go get more information. Then, within our ChirpBox here, the only property that is actually worried about this is where we have our cid here. So what we can do here is just say, first, we'll try to get the userId property. And this will work for our chirp list. However, if that doesn't work let's go to the user.cid property, which is what will work for our user objects themselves. That should work just fine. So just to make sure the right thing is doing the work here, let's go back to main and put our users after our chirps, once again. And now if we come back and refresh our page here, and now you can see our chirps are being displayed, exactly as we would expect. And if we go over to our users page, you can see that our users are using this box as well. Excellent. So we are successfully displaying a list of users and as a bonus, we can display it with the same component that displays our list of chirps. All right, so right now, we just have the text Follow Buttons in here, but in the next lesson, we'll look at creating actual follow and unfollow buttons for these blocks.