« Previous Tutorial Next Tutorial »

Today we’re going to create a log-out function for our app, which sounds like it should be easy … and it will be! But it’s also going to show how React apps can get complex very quickly, and will segue nicely into future tutorials in which we move to asynchronous actions and contacting our API in our action files instead of our components.

But that’s the future, and right now we’re talking about the present, in which the only way to reliably kill our user session is to restart our entire server. That seems … mildly like overkill, so let’s allow our users to log out. We’re going to build a couple of actions, tie them into a reducer, and then wire up our Header with a log out link that’ll fire off a call to the API and change the state. Sound good? Let’s go!

First off, open up /src/actions/authentication.js. We’re going to add two new action creator functions. I like to keep things alphabetical, so add them below line 3. Here they are:

export const logoutFailure = error => ({ type: 'AUTHENTICATION_LOGOUT_FAILURE', error });
export const logoutSuccess = () => ({ type: 'AUTHENTICATION_LOGOUT_SUCCESS' });

That’s all we need here, and by now it should be pretty obvious what those two functions are doing. We don’t have to pass anything with logoutSuccess because, well, there’s not going to be a returned user.

Save this file and open up /src/reducers/authentication.js. We need to listen for our two actions, here. We can piggyback on the log-in failure and session check failure cases for a log-out success because they create the desired effect: an empty authentication object in our Store. So, remove the opening brace from line 18 and then, below it, add this code:

    case 'AUTHENTICATION_LOGOUT_SUCCESS': {

Now we need to handle a logout failure. We’re going to give this one its own block, because we need to just return the existing state (after all, we failed to log the user out for some reason).

This is the kind of block you create because, if you’ve been programming for any significant amount of time, you know that “this shouldn’t ever really happen” means it’s definitely, definitely gonna happen at some point. That’s why we want this block. We’re putting it below line 33, and here it is:

    case 'AUTHENTICATION_LOGOUT_FAILURE': {
      // todo: handle error!
      return state;
    }

We haven’t gotten to error handling yet. That’s going to come soon, but after we do the cleanup I already mentioned. For right now, we’re done with this file. Save it, and let’s move on. We’re going to create a container file for our Header, because we’re about to get logical up in here. Create a new file in /src/components/shared called HeaderContainer.jsx. We’re going to start, as usual, with a bunch of imports. Here they are:

import React from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { incrementProgress, decrementProgress } from '../../actions/progress';
import { logoutFailure, logoutSuccess } from '../../actions/authentication';

import Header from './Header';

After that, it’s time to create a class. So, here’s the skeleton:

class HeaderContainer extends React.Component {
  constructor(props) {
    super(props);

    this.logUserOut = this.logUserOut.bind(this);
  }

  render() {
    const { authentication } = this.props;
    return (
      <Header authentication={authentication} logUserOut={this.logUserOut} />
    );
  }
}

You’ll probably note that we’re binding a “logUserOut” function that doesn’t actually exist (and also referencing it in our Header component in the render block). So let’s create that. Under your constructor block but above the render block, add the following. It should look pretty familiar:

  async logUserOut() {
    const {
      decrementProgressAction,
      incrementProgressAction,
      logoutFailureAction,
      logoutSuccessAction,
    } = this.props;

    // turn on spinner
    incrementProgressAction();

    // contact the API
    await fetch(
      // where to contact
      '/api/authentication/logout',
      // what to send
      {
        method: 'GET',
        credentials: 'same-origin',
      },
    )
    .then((response) => {
      if (response.status === 200) {
        return logoutSuccessAction();
      }
      return logoutFailureAction(`Error: ${response.status}`);
    })
    .catch((error) => {
      logoutFailureAction(error);
    });

    // turn off spinner
    decrementProgressAction();
  }

Now, are you starting to see the issue here? It’s frustrating and wasteful to have to pass our progress actions down to a component every time we want a button or link to do something. Also, this can lead to component tags with an unbelievable number of props in them. There are ways to mitigate this, including wrapping some of your props up in a master object or using variable destructuring, and we’ll get to those, but one of the best ways is to set up your app so you don’t have to pass some of these functions around over and over.

Anyway, I’m getting ahead of things again. For now, we need to connect these actions to Redux, which means a mapDispatchToProps and a connect. These lines go below your function:

function mapDispatchToProps(dispatch) {
  return bindActionCreators({
    incrementProgressAction: incrementProgress,
    decrementProgressAction: decrementProgress,
    logoutFailureAction: logoutFailure,
    logoutSuccessAction: logoutSuccess,
  }, dispatch);
}

export default connect(null, mapDispatchToProps)(HeaderContainer);

That’s it for this file. Save it. Now we need to change our Template over from calling our Header, to our Header Container, so open /src/components/Template.jsx and let’s make a few quick changes. Line 4 changes from:

import Header from './shared/Header';

to:

import HeaderContainer from './shared/HeaderContainer';

And line 14 from:

        <Header authentication={authentication} />

to:

        <HeaderContainer authentication={authentication} />

That’s it. Save the file, and open up /src/components/shared/Header.jsx. We need to make some modifications here in order to create and wire up a log out link. First we have to move renderGreeting into our class. I’ll explain why in a second. Delete line 6 entirely, and then under your constructor block, add the following:

  renderGreeting(name) {
    return (
      <span>
        Welcome, {name} | <a href="/logout" onClick={this.logOutClick}>Log Out</a>
      </span>
    );
  }

That this.logOutclick is why we need this functionality moved into the class. We’re about to create that method, and referencing it in an external function would be a pain.

Let’s create it. Just above renderGreeting (but still below the constructor), add the following:

  logOutClick(e) {
    e.preventDefault();
    const { logUserOut } = this.props;
    logUserOut();
  }

So … this seems weird, right? Why not just call logUserOut right from the link’s onClick attribute? The reason is because we can’t catch events in the parent container, so we’d get forwarded to /logout (a page that doesn’t exist) whenever we clicked. This would be a problem no matter if you used a pound sign, or any other URL in there. So in order to catch the click and run preventDefault() on it, we need this helper function.

Right, next up is to bind our two new functions. Just above line 11:

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

add these two lines:

    this.logOutClick = this.logOutClick.bind(this);
    this.renderGreeting = this.renderGreeting.bind(this);

One last thing we need to change. On line 50:

                { isLoggedIn ? renderGreeting(firstName) : renderLogin() }

add a this to renderGreeting, like this:

                { isLoggedIn ? this.renderGreeting(firstName) : renderLogin() }

That should do it! Save the file, and let’s test. Head for a browser. Our server’s probably restarted a bunch because we changed .js files even though they weren’t back-end files—hey, we should fix that sometime soon—and React may or may not have spammed your console with errors. I recommend a hard refresh here. Then perform a valid login, and click the resulting “log out” link in the header.

Nice. We can log in and log out. Next time, we’re going to do some code reorganization. See you then!

« Previous Tutorial Next Tutorial »