« Previous Tutorial Next Tutorial »

Now that we’re running on the latest and greatest versions of Node and React, let’s get back to coding. Specifically, we’re going to make our homepage into something at least slightly more interesting than it currently is, and we’re going to clean up a bit of code while we’re at it.

First we’re going to build a little action that snags the latest album uploaded to Discogs, just for fun. Head for /src/actions/albums.js and below line 9, add the following code:

export const albumLatestFailure = error => ({ type: 'MUSIC_ALBUM_LATEST_FAILURE', error });
export const albumLatestSuccess = json => ({ type: 'MUSIC_ALBUM_LATEST_SUCCESS', json });

Then head down to line 104, add a padding line, and create a getLatestAlbum function like this:

// Get the latest album from the Discogs API
export function getLatestAlbum() {
  return async (dispatch) => {
    // turn on spinner
    dispatch(incrementProgress());

    // Build packet to send to Discogs API
    const searchQuery = {
      q: '',
      type: 'master',
      format: 'album',
      sort_order: 'asc',
    };

    // Send packet to our API, which will communicate with Discogs
    await fetch(
      // where to contact
      '/api/albums/search',
      // what to send
      {
        method: 'POST',
        body: JSON.stringify(searchQuery),
        headers: {
          'Content-Type': 'application/json',
        },
        credentials: 'same-origin',
      },
    )
    .then((response) => {
      if (response.status === 200) {
        return response.json();
      }
      return null;
    })
    .then((json) => {
      if (json.results) {
        return dispatch(albumLatestSuccess(json.results[0]));
      }
      return dispatch(albumLatestFailure(new Error(json.error)));
    })
    .catch(error => dispatch(albumLatestFailure(new Error(error))));

    // turn off spinner
    return dispatch(decrementProgress());
  };
}

Save the file, and let’s create a reducer to listen for those actions. Note: we’re not going to update our error reducer for this one, because we don’t actually want to display an error if the call to Discogs fails. We’ll just display an empty box instead, and it’ll be fine.

So, create a file in /src/reducers named latest.js, and in it put the following code:

const initialState = {};
export default function reducer(state = initialState, action) {
  switch (action.type) {
    case 'MUSIC_ALBUM_LATEST_SUCCESS': {
      const newState = action.json;
      return newState;
    }
    case 'MUSIC_ALBUM_LATEST_FAILURE': {
      const newState = Object.assign({}, initialState);
      return newState;
    }
    default: {
      return state;
    }
  }
}

We’re good here, so save the file and head for /src/reducers/index.js. Underneath line 5, add this code:

import LatestReducer from '../reducers/latest';

And underneath line 15, add this:

  latest: LatestReducer,

Save that file and we’re ready to go. Open /src/components/home/HomePageContainer.jsx. We’re going to get rid of those increment/decrement buttons, so we don’t need the associated actions anymore. Change line 5 to this:

import { getLatestAlbum } from '../../actions/albums';

Now let’s nuke our old mapDispatchToProps function entirely and replace it with an ES2015 version, and a mapStateToProps. So remove lines 18 to 23, and replace them with this code:

const mapDispatchToProps = dispatch => bindActionCreators({
  getLatestAlbumFunction: getLatestAlbum,
  dispatch,
}, dispatch);
const mapStateToProps = state => ({ latestAlbum: state.latest });

Then change line 24 to this:

export default connect(mapStateToProps, mapDispatchToProps)(HomePageContainer);

Now we’re going to switch our component to a class so that we can use a lifecycle method to load the latest album before the component loads. Replace the entire function, lines 8 to 16, with this code:

export class HomePageContainer extends React.Component {
  componentWillMount() {
    const { getLatestAlbumFunction } = this.props;
    getLatestAlbumFunction();
  }

  render() {
    const { latestAlbum } = this.props;
    return (
      <HomePage
        latestAlbum={latestAlbum}
      />
    );
  }
}

We’re good here, so let’s drill on down. Save the file and open /src/components/home/HomePage.jsx. We’re going to put some text here, and pass our latest album info to the sidebar component. First, change line 2 to the following:

import { Link } from 'react-router-dom';

Then change line 7 to this:

  const { latestAlbum } = props;

Now replace the buttons on lines 11 and 12 with all of this:

        <h1>Welcome to MusicList</h1>
        <p>
          This is a simple React app where you can look up artists you like and albums you own,
          and add them to your list. Got rid of an album or decided you're not that fond of an
          artist? Just remove them.
        </p>
        <ul>
          <li><h2><Link to="/artists">Search Artists</Link></h2></li>
          <li><h2><Link to="/albums">Search Albums</Link></h2></li>
        </ul>

And finally, change the sidebar line, which is now line 22, to this:

      <Sidebar latestAlbum={latestAlbum} />

Done here, so save the file. Open /src/components/shared/Sidebar.jsx. Change line 2 to this:

import { Card, CardBlock, CardText, CardTitle } from 'reactstrap';

Then we’re going to add a function that formats our latest album info (assuming there’s any info to format). Otherwise it just returns nothing. Below line 2, add a padding line, and then this code:

const formatAlbum = (album) => {
  if (!album) {
    return null;
  }

  return (
    <span className="text-center">
      <img src={album.thumb} alt="album thumb" /><br />
      { album.title }
    </span>
  );
};

Now modify line 17 to take a props argument like this:

export default function Sidebar(props) {

And directly below it, add this line:

  const { latestAlbum } = props;

Now add a title below line 22 line this:

          <CardTitle className="text-center">Latest Album</CardTitle>

And change lines 24 to 26 to these:

          <CardText className="text-center">
            { latestAlbum && latestAlbum.title ? formatAlbum(latestAlbum) : null }
          </CardText>

That’s it for new stuff. Save the file. Now we’re going to clean up a few things. Specifically, our album and artist search forms. Open up /src/albums/AlbumsPage.jsx. We don’t need the handleKeyPress method anymore. Why not? Because when we switched over to Availity-Reactstrap for validation, we got enter handling thrown in as a bonus. So delete line 17 entirely, and then delete lines 31 to 37, the handleKeyPress method and the padding line below it.

We don’t have to add anything here, so save the file. We're going to do this same thing with a bunch of other files. Open /src/components/artists/ArtistsPage.jsx. We’re doing the same thing here, so delete line 13, and then delete lines 27 to 33. Save the file, then open /src/components/account/ChangePasswordPage.jsx, remove line 12, and then lines 26 to 32. Save the file, and open /src/components/account/LoginPage.jsx, remove line 12 and then delete lines 27 to 32. Save that file and open /src/components/account/RegisterPage.jsx, remove line 11 and then delete lines 28 to 34. Save that file and, finally, open /src/components/account/ResetPasswordPage.jsx. Delete line 12, and then nuke lines 40 to 45.

You’re done! Save the file, head for a browser, and refresh your homepage. You should now get some helpful text and a not-particularly-helpful-but-mildly-amusing Latest Album sidebar. Everything else should be working the same.

That’s … that’s it, guys. Anything more I could add to this would really only be running you through the same stuff we’ve already learned. So the time has come to stop adding features, and focus instead of cleanup, preparation and optimization for production deployment, and, well … production deployment. I’d say that’ll take at least five more tutorials, and maybe ten, but we’re getting real close to the end of Five Minute React, now. I’m excited to wrap it up.

In the next tutorial, we’re going to go back to a subject we spent some time on earlier: Webpack optimization. See you there!

« Previous Tutorial Next Tutorial »