React and Redux Tutorial Trending GitHub – Part 4

Trending Repos - Featured Image

This is the fourth post in a series titled “React and Redux Tutorial Trending GitHub”. It is my attempt to teach what I’ve learnt about Redux to make sure I’ve actually learned it.

The series is aimed at beginners. The final app doesn’t need Redux but I used it as a stepping stone to more complex apps. I rate Todo Apps, but I wanted to create something I couldn’t just cut and paste away my problems if I got stuck. I encourage you not to do the same.

Over the course of the series, I’m gradually building an app that looks like the image at the top of this post. The series is made up of the following posts:

  1. Introduction, setup and add a Header component
  2. Add static data to Redux
  3. Display the static data nicely
  4. Load data from an API during application start (this post)
  5. Add a Search Button

So far, we’ve got an app that displays data from the GitHub API in a nice bootstrap table. Unfortunately, the data was obtained a while ages ago, so the app has little use. In this post, we’re going to retrieve some real-time data at application start.

Rather than call the API directly from our app, I’m going to use another idea from Cory House, mock APIs.

Creating out Mock API

I saw Cory House use this technique in a PlualSight video and since using it myself I’m sold. Using this idea allows you to develop against a local mock that you have direct control over. This will speed up development and allow you to develop without an external connection. When you’re ready, you can switch one import statement to point to the real API and you’re done.

Create a file called mockGitHubAPI.js in src/api with the following contents.

const delay = 1000;

const repos = [ 
	// a refresh of data from postman OR an edited copy of initialState
];

class GithubApi {
  static getAllRepos() {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve(Object.assign([], repos));
      }, delay);
    });
  }
}

export default GithubApi;

This API has one method, getAllRepos, that returns the repos array. There’s a couple things to note here:

  • Use a different set of data in the repos variable defined in this file. Otherwise, we won’t be able to tell when our app has fetched the data.
  • getAllRepos returns a Promise. If you don’t know what they are David Walsh does a much better job of explaining them than I ever could.
  • The API has a built-in delay of 1 second (1000 ms). This is great to show how your app will response in high latency environment.

Now the API is setup, we need to call it. We will use a part of Redux called middleware for Async Action and in particular redux-thunk. This requires a little bit of config.

Add Redux-Thunk Middleware

Switch back to a command prompt and install redux-thunk with

> npm install --save-dev redux-thunk

We need to add this middleware to our Redux store, so edit src\store\configure.dev.store.js to:

import {createStore, applyMiddleware, compose} from 'redux';
import rootReducer from '../reducers';
import thunkMiddleware from 'redux-thunk';
export default function configureStore(initialState) {
  const store = createStore(rootReducer, initialState, compose(
    // Add other middleware on this line...
    applyMiddleware(thunkMiddleware),
    window.devToolsExtension ? window.devToolsExtension() : f => f // add support for Redux dev tools
    )
  );

  if (module.hot) {
    // Enable Webpack hot module replacement for reducers
    module.hot.accept('../reducers', () => {
      const nextReducer = require('../reducers').default; // eslint-disable-line global-require
      store.replaceReducer(nextReducer);
    });
  }

  return store;
}

And that’s it! Redux should now be able to handle Async Actions. So let’s create our first action.

Create an Async Action

As we don’t have any actions yet, create a new file in src/actions called index.js with the following code:

import GithubApi from '../api/mockGithubApi';

function receiveRepos(json) {
  return {
    type: 'RECEIVE_POSTS',
    repos: json
  };
}

export function fetchRepos() {
  return dispatch => {
    return GithubApi.getAllRepos()
      .then(json => dispatch(receiveRepos(json)));
  };
}

If you’re just cutting and pasting code, feel free to move to the . Otherwise, I’ll try and explain.

The first action, receiveRepos is designed to be used once we have a blob of JSON that represents our repos. It is not meant to be called from within a component, but there is nothing stopping you.

The second action, fetchRepos is meant to be called from within a component. It doesn’t return a JavaScript object like standard Redux actions. Instead, it returns a function that takes dispatch as a parameter. Unless you really want to, don’t worry how this happens. It works due to the redux-thunk middleware we added.

The intended flow is:

  1. A component dispatches a fetchRepos action
  2. Redux, with the help of redux-thunk, asynchronously calls our API
  3. If the API call is successful, the receiveRepos action is dispatched with the API result
  4. Our reducers process the action from receiveRepos

Before we call receiveRepos we need to update our reducer.

Update Reducer

We only have one reducer at the moment, so change src/reducers/reposReducer.js to:

import initialState from './initialState';

export default function reposReducer(state = initialState.repos, action) {
  switch(action.type) {
    case 'RECEIVE_POSTS':
      return action.repos;
    default:
      return state;
  }
}

We’re not changing the data we receive from our API call in any way. Therefore the reducer sets the new state as the JSON in the action.

That’s it for plumbing Redux, all the remains is to call our new fetchRepos action. But from where?

React LifeCycle Methods

React provides a number of life cycle methods. These are called by react at different times during the application loading and running. We are interested in one called componentWillMount. We use this method as it’s called before anything is rendered.

Update reposList.js in src/components to

import React, {PropTypes} from 'react';
import ReposRow from './ReposRow';
import {fetchRepos} from '../actions';

class ReposList extends React.Component {

  constructor(props) {
    super(props);
  }

  componentWillMount() {
    this.props.dispatch(fetchRepos());
  }

  render() {
    let {repos} = this.props;

    return (
      <div className="table-responsive">
        <table className="table table-hover">
          <thead>
            <tr>
              <th>Avatar</th>
              <th>Fullname</th>
              <th>Stars</th>
              <th>Language</th>
            </tr>
          </thead>
          <tbody>
            {repos.map(repos =>
              <ReposRow key={repos.id} repos={repos} />
            )
            }
          </tbody>
        </table>
      </div>
    );
  }
}

ReposList.propTypes = {
  dispatch: PropTypes.func.isRequired,
  repos: PropTypes.array.isRequired
};

export default ReposList;

That should be it. Switch back to your browser and refresh the app. You should see the data displayed is not the data from our initialState.js. It should be our data from src/actions/index.js. Let’s now fetch live data.

The user experience isn’t great. The data in initialState.js is flashed on the screen before the API returns. You can make this better if you delete the array from initialState.js if you wish.

Live Data

Create a new file in the src/api directory called GitHubAPI.js with:

class GithubApi {
    static getAllRepos() {
        let dateFrom = (new Date()).toISOString().slice(0, 10);

        return fetch(`https://api.github.com/search/repositories?q=created:>=` + dateFrom + `&sort=stars&order=desc&per_page=10`)
            .then(response => response.json())
            .then(json => json.items);
    }
}

export default GithubApi;

This is where we finally call the GitHub API in code. You can see the query is the same as we used in Postman. The only difference is that we’re calculating the date at runtime to be today.

All that’s left is to update the app to use the real API by changing src/actions/index.js to import this file, rather than the mock API.

Switch back to your browser and refresh the app. You should now see the latest trending information live from GitHub.

End of Part 4

If you’ve made it this far, thank you for sticking with it. Our app is now usable and we’ve achieved most of what we set out to do. We’re receiving data from GitHub and displaying it. There are still some things we can improve upon. For instance, the user can’t refresh the data without refreshing the whole page. The app also doesn’t allow the user to filter by language. Next time, we’ll fix these shortcomings by adding a “Search Language” option.

If you have any comments, questions or problems, please let me know below or contact me on twitter.

No comments yet, your thoughts are welcome.

Leave a Reply

Your email address will not be published. Required fields are marked *