2.7 Page Component
The obvious next step is to render the pages themselves. So, let's create our
1.Getting Started6 lessons, 27:04
2.Building the Application13 lessons, 1:29:43
3.Conclusion1 lesson, 00:44
2.7 Page Component
In our previous lesson we finished the page list component which allows our users to see a list of all the available pages. And of course they get links that they can click on to navigate to those pages. Right now here in our abduct JS file. We have our page wrote using the app component as the handler. However, we want to create a custom page component. So I'll change the handler name to page. And then at the top let's import page from components/page. All right. So now, let's go ahead and create that file. We'll import React in the API, as we've done before. And then we'll go ahead and export our page class. The first thing we want to set is some state. We'll set the page state property to be an empty object for now. Just like we did with the page list, this object will be populated when the component mounts by making a call to the firebase data store. So let's add a componentDivMount function. In here we're going to get api.pages to get our pages reference. But then we want to get a sub reference. We don't want all of the pages, we just want the one page that we're trying to display here. So we can do api.pages.child and we can pass in this.props.params.id. Now we've seen this stop props before. But where is this params object coming from? Or remember this component is going to be rendered as a route. And so if we look back at our router here, you can see that in the path, we have a parameter ID, referenced of course by the token ID. So when the page component is rendered. The rotor will give that component a params object as a property and that will have the ID property. Once we have this child reference, we can call .on('value'), and this way we're listening for changes on this specific value in the database. And when we get fresh data for this page, we'll call this.updatecontent. So let's go ahead and write that function next. This to update content will take a snapshot as it's only parameter. We can get the json value of that snapshot by doing snapshot.exportVal. And then we can call this.setState. And we'll set two things here. We'll set the page to be all of the json. However, the content of the page is going to be stored in sections. So let's give ourselves a shortcut to that sections object, and we'll set the sections' state value to be json.sections. Now there's some tricky React behavior here that we have to plan for. You might think that each time we click one of these links in our page list, our page component on the right will rerender entirely, and so will set up a fresh event handler. However this isn't the case. What actually happens is the React Router will use the page list component that we already have rendered. And instead of re-rendering it, it will just pass at the new parameters. Basically it's like updating the properties of a component. This is actually how our page list, Create new page, text box works. When the user logs in or out, we change the value of the property that's being passed to the page list component. And the page list component itself updates appropriately. However with our page component here, it's a little bit different. We don't actually render the parameter data that is sent in to us from react router. Instead we use that data to fetch new data that we render. So what we have to do here in our page class is add a component, WillReceiveProps function. And this takes the next set of props as a parameter. React calls this function whenever our component receives new properties, but before it re-renders because of those properties. So that means within this function, we can update our state. So what we have to do inside this function is two things. First of all we have to stop listening for the value of ent on the page that we were rendering, and then we have to start listening for the value of ent on the page that we're about to render. So we can do this through the off function as you would expect with any event emitter object. So we can say API.pages.child, and we'll pass in this.props.params.id. Now at this point in the process, this.props still refers to whatever the current properties of the component are. So this is going to be the old ID. And we can call dog off. And we will stop listening to the value of vent. And of course we have to pass that update content function. So that it will know which events listener to unhook. Now the next line that we want to write is very similar. It's going to be API.pages.child. And this time we're going to do it next prop stop perhaps dot id and next props, of course, is the parameter that was passed to component WillReceiveProps. So these are the new properties coming in from the route change. So we'll say on value. We want to call this .updateContent. And of course, this will act in the very same way that our on value of apt handlers have previously, it will get the initial value of the data right now, and then it will send us any changes that are made to that data in the future. So this is how we can switch from one page to the next. All right, with all this in place let's create our render function. The primary chunk of our page is going to be the rendering of the different sections of our page. So I'll create an array here called sections. However we're not going to worry about rendering sections until the next lesson. For now let's look at rendering our header. So I'm going to return our article element. And then within this element, we can create an h1. The value that we want to put inside this h1 will be this.state.Page.title which will be the title of our page. However if this.state.page.title does not yet exist, that means the data hasn't come in yet. So, we will display loading instead, if there's no page.title value. So if I come here to our application and refresh the page, notice that we can click on the different links, and the heading for those pages displays over on the right hand side. Now, we're not seeing our loading sign here, because by the time we can see these links to click them, the page data has already come from firebase. And the firebase component seems to be pretty smart about using caching to speed up our data requests. However if instead I go ahead and refresh the page, you can see that very briefly we saw the text loading up there at the top before we saw the text cats. So that means our primitive loading behavior is working just fine. Now like I said the main portion of our page is going to be the sections. However at the very bottom of our page we do want to include an ad section button. So that users can add a new section to the page if they want to. So we're going to go ahead and add that button next. We're gonna start with an if statement here, and we're going to only do this if this.state.page.title exists. This is a lot like the loaded flag that we used in the page list component. The idea here is that if the page data hasn't come back from the server yet, we don't want to allow the user to add a section just yet. We wanna make sure that we're displaying the current data before we try and add new data to the database. So if this.state.page.title exists, then we know that we do have that data. Now the first thing we're gonna do inside this if statement is render the sections. However, I'll just leave the comment there and we'll come back to this in a later lesson. The second thing we want to do in here is add the add section button. However we only want to do this if the user is logged in. So we'll add another if statement here that says if this.props.user. So will only add the add section button if the user is logged in. Now will just push our add section button into sections array. So I'll say sections to push or push in a paragraph. And will give it its own key. Inside this paragraph will add a button and it will of course have an on click handler. And will call the this.addSection. Let's give our button the text Add Section as well, and then down here in our return statement underneath our h1, let's render our sections array. So if we come back and refresh our page, you can see that our Add Section button is being displayed. And you can also see that, while we have the text loading the button does not display. Which is exactly the behavior that we want. So, it looks like this button is showing up just fine. Notice also that if I log out, the button disappears. So that means that users who are not logged in Can't add sections to our Wiki pages, excellent. Notice also that the new title page box disappears as well, which means that visitors who aren't logged in can't create new pages either. If I log back in, you can see that right away both of those editing functionalities are made available again, that's pretty cool. All right. So back in our code here, we'll finish off our lesson by writing the add section function. Inside of this function, we need to start by creating an ID for our section. This time, instead of allowing Firebase to automatically create IDs for our sections, we're going to give our sections their own automatically incrementing integer IDs. However we can't just define an ID as a number. We have to figure out what that number should be. Of course, the special case here is, what if this is the first section that we're creating in an empty page? So we can find that out by saying If this.state.sections and I'm including an exclamation point at the beginning of this. So we're saying, if this.state.sections is not defined. If, when we click the Add Section button, there are currently no sections in the page, then what do we want to do? Well we'll set the id equal to 1 because this is the first section. And then we'll set this .state.sections equal to an empty object. And so we can initialize that object full of sections. Now if this .state.sections does exist, we need to find the last ID that we used and increment that by one to get our new ID. But we can use another ES 6 feature here. So I'll set the id = Math.max. And we'll use three dots, which is the spread operator to spread an array out as a parameters in a function call. And the array that we wanna spread out will be Object.keys(this.state.sections));. So we'll get the number keys for all the current sections. Figure out which one is the max. And then increment it by one. All right so after all that. Inside of our and section function here, we know that we have the idea number that we need. And we know, so that this dot state dot sections will be an object. So now we can just add our section. To this dot state dot sections. So I'll say this .state.sections. Square bracket ID. And that will be a new object. Now we can't push an empty object into our firebase data store. And we don't have any content for this section yet either. However what we can add as a property is the editor. And the editor will resist our props dot user dot username. Now the reason that we're giving a section an editor property is because remember this is a live collaborative wiki. So the idea is eventually users will be able to edit it on the go. And other users who are viewing the page will automatically see their changes. However, we don't want two users to be able to edit the same section at the same time. So we need sections to have an editor, if they're currently being edited. This way we can show other users that someone else is currently editing that section and disable editing for them for that section. Now you might think that the next step here would be to store this section in the database, however, that's not quite true. What we want to do is display this new empty section, so that the user can add some content to it. However until the user add some content to it, we don't want to save it to the database. Right. So at this point we just need to update our page state. So that it will know to render a new empty section in editing mode of the bottom of the page. And then when the user as content to that section. Will be able to save it. So all that to say that the next step is not to stored in the database. But to update our components state. So we'll say this dots that state and will set sections equal to this dot state dot sections. Of course we just updated the state DOT sections object in place. But now we need to let the component know about that. Now unfortunately at this point I can't really show you that this add section function is working because the only thing we've done really in this add section function is change the state of our state DOT sections property. However we're not rendering those sections yet. So if I click that add section button. Nothing's going to change in our page. So you're going to have to wait until the next lesson. Where we will begin looking at rendering sections.