« Previous Tutorial Next Tutorial »

All right, we’re back and ready to start building what we need for a Log In page. First, let’s use our new Bootstrap capabilities to make our navigation a little less terrible. We’re going to add a Reactstrap Navbar to our header, complete with toggling for small displays, which will allow us to cover two things we haven’t touched on yet: React classes, and component state. Fair warning: this tutorial runs a little long, but I felt it’d be more confusing to throw a bunch of code at you in one tutorial, then explain it all in a second. So … buckle in!

First thing we need to do is a little housekeeping. We’re duplicating a plugin right now in our babel config and our webpack config, and it can lead to some state issues, so open .babelrc and just delete this line entirely:

"plugins": ["react-hot-loader/babel"]

That’s it. Just save the file and we’re good. Now open /src/components/shared/Header.jsx and let’s make some changes. Below line 2, add the following:

import { Collapse, Navbar, NavbarToggler, NavbarBrand, Nav, NavItem, NavLink } from 'reactstrap';

As you can see, we’re pulling in a whole bunch of stuff from Reactstrap that we’re going to use to assemble our Navbar. Now, here’s where things change significantly. In order to make toggling work, we need to use an existing method that’s only available to React classes, not pure functions like we have here, so it’s time to build our first one of those. Here’s what I recommend: you should add a few linebreaks above line 4, export default function Header(props) {, and then write out the entire new class above it. That way, you can compare the two and note differences between the function and the class.

We’re going to be using ES6 classes, here. They’re pretty straightforward, but we should talk about them a little bit. Classes are special objects that basically put a new layer over JavaScript’s existing prototype-based inheritance that makes things a little easier to work with. They’re great for defining an object (in this case a React component) and making it clear how inheritance works. Classes can extend other classes, which means they get all of the parent class’s existing methods and other parameters, but can also add their own, which don’t get passed up to the parent. In this case, we’re extending React’s Component class, which means we get a bunch of handy methods like setState, which we’ll be using today, various lifecycle methods like componentWillMount that we’ll be covering in later tutorials, and more.

Let’s start by declaring our new class. It’s not that different from our function declaration. Here it is:

export default class Header extends React.Component {
}

As you can see, we’ve swapped function out for class, and we’re extending React.Component. OK, fair enough. Now let’s add some lines at the top of the class:

  constructor(props) {
    super(props);

    this.state = {
      isOpen: false,
    };
  }

All right, that might be mildly alarming. A class constructor tells the class what arguments the class can take. It’s similar to defining function arguments like this:

function(arg1, arg2) {
}

Except we’re doing it inside the class declaration. In most cases, inside a constructor, you then need to assign the arguments to this.whateverNameYouLike … so you’d expect there to be a line like this:

this.props = props;

… but there’s not. That’s because the actual next line, super (props), is taking the assignments from the parent class (React.Component). In order to use “this” properly in a child class, you need to call super in the constructor. All of this is higher-level than we can really dive into in this tutorial, so if you want more information, I once again recommend Wes Bos’s ES6 For Everyone, which has an entire section on classes. It’s a great set of courses.

Moving on, we see that we’re setting a state variable for our class. This is the only place you should ever manipulate this.state directly … in all other cases you’ll use a special setState() method, which we’ll get to. In our case, we’re just setting a variable called “isOpen” to false. This will control whether or not the navbar starts out expanded or collapsed on small screens (we want it to be collapsed). Doing this in the constructor means that this.state and this.state.isOpen will be available anywhere else within the class that we want to use it.

All right, let’s talk state. We covered app state a while back in Five Minute React 29. App state is different from component state. The former contains data available application-wide. The latter controls the particular state of bits and pieces of a given component. As a general rule of thumb, if it’s coming in from your database, it should be stored in app state. If it’s just visual toggles like a navbar expanding or collapsing, that’s a good candidate for component state. We’ll use both types of state a lot in this app, giving you plenty of chances to learn the difference.

All right, add a line break after line 12 for some spacing, and then add this code to your class:

  render() {
    return (
      <header className="wrapper">
        <Navbar color="faded" light toggleable>
          <NavbarToggler right onClick={this.toggleNavbar} />
          <NavbarBrand tag={Link} to="/">MusicList</NavbarBrand>
          <Collapse isOpen={this.state.isOpen} navbar>
            <Nav className="ml-auto" navbar>
              <NavItem>
                <NavLink tag={Link} to="/account/login">Log In</NavLink>
              </NavItem>
            </Nav>
          </Collapse>
        </Navbar>
      </header>
    );
  }

Here we create a render method (which React will run automatically when it mounts the component) that returns a whole bunch of JSX, most of it comprised of the various components we imported from Reactstrap. This is going to give us a Boostrap navbar that will say “MusicList” in the upper left, and have a “Log In” link in the upper right, the latter of which will be hidden on small displays unless the user clicks a menu button. A few things to note:

On line 18 we’re calling a this.toggleNavbar method when a user clicks. Observant readers might note that we have not actually defined that method yet. We’ll fix that in a second. Also note that on line 20 we’re using this.state.isOpen which means that if we toggle that value in our state, the value will change in React, which will cause a redraw, changing the classes necessary to expand or collapse the menu. Cool!

All right, let’s add our toggle method. Above the render block, but below the constructor, add the following code:

  toggleNavbar() {
    this.setState({
      isOpen: !this.state.isOpen,
    });
  }

As you can see from this and the render method, we don’t have to use the “function” declaration or anything in ES6 classes. It’s just the method name and some parens (which would contain arguments, if we needed them, but here we don’t).

So there’s how setState() works. You just pass it an object with any parts of the component state you want to manipulate, and it’ll do so. This is nice because it means you could have, say, five parameters defined in this.state, but only manipulate two of them using setState, and the other three just stay as they were. Due to React’s automagic, this also triggers a component re-render, which is one of the reasons why you always want to use setState() rather than manually changing this.state.

All right, seems like we should be done, but we’re not. Helper methods like toggleNavbar have to be bound in our constructor. There’s a big, long reason for this, but the shorter version is that if you don’t bind them, React will basically create a new instance of toggleNavbar every time the component re-renders, but when you click on the toggle it’ll be running the old instance of toggleNavbar attached to the old version of the component, which no longer exists. That’ll throw an error. It’s a little confusing, but the long and short of it is, we need this line up in our constructor, just above this.state:

this.toggleNavbar = this.toggleNavbar.bind(this);

Cool, now we’re all set. Except, of course, we need to delete the old Header function. Do that whenever you’ve compared it to the class and feel comfortable that you understand the differences. For reference, the final file should look like this:

import React from 'react';
import { Link } from 'react-router-dom';
import { Collapse, Navbar, NavbarToggler, NavbarBrand, Nav, NavItem, NavLink } from 'reactstrap';

export default class Header extends React.Component {
  constructor(props) {
    super(props);

    this.toggleNavbar = this.toggleNavbar.bind(this);
    this.state = {
      isOpen: false,
    };
  }

  toggleNavbar() {
    this.setState({
      isOpen: !this.state.isOpen,
    });
  }

  render() {
    return (
      <header className="wrapper">
        <Navbar color="faded" light toggleable>
          <NavbarToggler right onClick={this.toggleNavbar} />
          <NavbarBrand tag={Link} to="/">MusicList</NavbarBrand>
          <Collapse isOpen={this.state.isOpen} navbar>
            <Nav className="ml-auto" navbar>
              <NavItem>
                <NavLink tag={Link} to="/account/login">Log In</NavLink>
              </NavItem>
            </Nav>
          </Collapse>
        </Navbar>
      </header>
    );
  }
}

Save this file and let’s head over to localhost:3000. You may need to hard-refresh, or even restart your server, because we made those changes to .babelrc. You should have a fully-working navbar, and you can resize your browser down to see the toggle functioning properly as well.

Great! Now we have our Navbar. In the next tutorial, we’ll make that Log In link actually go somewhere!

« Previous Tutorial Next Tutorial »