DEV Community

Daniel Deutsch
Daniel Deutsch

Posted on

Fetching Github with React and Redux


Photo by Osman Rana on Unsplash

As with everything in life only practice makes you good in a certain field. Therefore I decided to create another application using React and Redux. This time I wanted to focus on asynchronous action flow with Redux, which is a little different from the synchronous dispatching process.

➡️ Github Repo is available here ⬅️


“Think Big And Don’t Listen To People Who Tell You It Can’t Be Done. Life’s Too Short To Think Small. - Tim Ferriss

What I am going to build

I am going to build a simple app, that fetches repositories from Github by typing the name of the Github user:

gif

The building process

To quickstart the the configuration I used the React Slingshot boilerplate by Cory House. It provides nice linting and feedback during the whole building process.

First I started out with defining basic React Components. I used the provided structure and adapted it for a home page and an about page.
For jumping across routes I also used the provided React Router features because it's simple and fast.

The next step was adding some basic styling. I wanted to use Material-UI but quickly realized that I have to dive into the framework. After some minutes with bugs, I decided to stay with MaterializeCSS, which I used in the past. It provides great documentation and simple CSS components. It's the CSS framework I enjoy working with the most.

The Redux process

After that I wired up a basic Redux flow, providing a store, actions and a reducer. One way when working async in Redux is to use redux-thunk. I have choosen this way because it's fast and reliable. (I didn't want to tackle Redux-Saga, since I need more knowledge on Promises)

From redux-thunk's docs:

Redux Thunk middleware allows you to write action creators that return a function instead of an action. The thunk can be used to delay the dispatch of an action, or to dispatch only if a certain condition is met. The inner function receives the store methods dispatch and getState as parameters.

That's the whole magic. Returning a function instead an action. It allows to wait for an answer after a http call (or whatever call) and dispatching the action after receiving the data.

The code looked like:

//Action
import axios from 'axios';
import * as types from './actionTypes';

export function loadReposSuccess(repos) {
    return {
        type: types.LOAD_REPOS_SUCCESS,
        repos
    };
}

export function loadRepos() {
    return function(dispatch) {
        return axios
            .get('https://api.github.com/users/DDCreationStudios/repos')
            .then(repos => {
                dispatch(loadReposSuccess(repos.data));
                console.warn(repos.data);
            })
            .catch(err => {
                throw err;
            });
    };
}

Enter fullscreen mode Exit fullscreen mode
//index.js
import React from 'react';
import { render } from 'react-dom';
import { Router, browserHistory } from 'react-router';
import 'materialize-css/dist/css/materialize.min.css';
import { Provider } from 'react-redux';

import routes from './routes';
import configureStore from './store/configureStore';
import { loadRepos } from './actions/reposAction';

const store = configureStore();
store.dispatch(loadRepos());

render(
    <Provider store={store}>
        <Router history={browserHistory} routes={routes} />
    </Provider>,
    document.getElementById('app')
);

Enter fullscreen mode Exit fullscreen mode
//reducer
import * as types from '../actions/actionTypes';

export default function reposReducer(state = [], action) {
    switch (action.type) {
        case types.LOAD_REPOS_SUCCESS: {
            return action.repos;
        }
        default:
            return state;
    }
}
Enter fullscreen mode Exit fullscreen mode

On a side note: I used axios because I initially thought I would create a bigger app. A simple "fetch" method would have been sufficient as well. :)

Building the search feature with Redux

This was a little bit more complicated, since I needed to make the fetch depended on another user action. But that's why Redux is so great.

The key thing was to regulate the flow with the store in the index.js, because I wanted to subscribe to the store and only dispatch an action when a certain change in state has occured. I found the "handleChange" helper function as solution:

//index.js
import React from 'react';
import { render } from 'react-dom';
import { Router, browserHistory } from 'react-router';
import 'materialize-css/dist/css/materialize.min.css';
import { Provider } from 'react-redux';

import routes from './routes';
import configureStore from './store/configureStore';
import { loadRepos } from './actions/reposAction';

let currentValue;
function handleChange() {
    let previousValue = currentValue;
    currentValue = store.getState().user;

    if (previousValue !== currentValue) {
        store.dispatch(loadRepos(store.getState().user));
    }
}

const store = configureStore();
store.dispatch(loadRepos(store.getState().user));
store.subscribe(handleChange);

render(
    <Provider store={store}>
        <Router history={browserHistory} routes={routes} />
    </Provider>,
    document.getElementById('app')
);
Enter fullscreen mode Exit fullscreen mode

Now the fetching of data was called only when the state of user changed in the store. Heureka!

Then I adapted the other files accordingly:

//reducer index.js

import { combineReducers } from 'redux';

import repos from './reposReducer';
import user from './userReducer';

const rootReducer = combineReducers({
    repos,
    user
});

export default rootReducer;
Enter fullscreen mode Exit fullscreen mode
//initialState.js
export default {
    repos: [],
    user: 'DDCreationStudios'
};
Enter fullscreen mode Exit fullscreen mode
//updated repo reducer
import * as types from '../actions/actionTypes';
import initialState from './initialState';

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

Enter fullscreen mode Exit fullscreen mode
//user reducer
import * as types from '../actions/actionTypes';
import initialState from './initialState';

export default function userReducer(state = initialState.user, action) {
    switch (action.type) {
        case types.LOAD_USER_SUCCESS: {
            return action.user;
        }
        default:
            return state;
    }
}
Enter fullscreen mode Exit fullscreen mode
//user action
import axios from 'axios';
import * as types from './actionTypes';

export function loadUser(user) {
    return {
        type: types.LOAD_USER_SUCCESS,
        user
    };
}
Enter fullscreen mode Exit fullscreen mode
//updated repo action
import axios from 'axios';
import * as types from './actionTypes';

export function loadReposSuccess(repos) {
    return {
        type: types.LOAD_REPOS_SUCCESS,
        repos
    };
}

export function loadRepos(user) {
    return function(dispatch) {
        return axios
            .get(`https://api.github.com/users/${user}/repos`)
            .then(repos => {
                dispatch(loadReposSuccess(repos.data));
                console.log("receiving following data: "+repos.data);
            })
            .catch(err => {
                throw err;
            });
    };
}
Enter fullscreen mode Exit fullscreen mode
//actionTypes
export const LOAD_REPOS_SUCCESS = 'LOAD_REPOS_SUCCESS';
export const LOAD_USER_SUCCESS = 'LOAD_USER_SUCCESS';
Enter fullscreen mode Exit fullscreen mode

And that's it!


Wire up the corresponding container component, in this case the "HomePage", and and it works great:

// HomePage.js
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';

import * as userActions from '../../actions/userAction';
import Repo from './Repo';
import Searchbar from './Searchbar';

class HomePage extends Component {
    constructor(props) {
        super(props);
        this.state = {
            user: ""
        };
        this.updateSearch = this.updateSearch.bind(this);
        this.saveSearch = this.saveSearch.bind(this);
    }

    updateSearch(e) {
        let user = e.target.value;
        return this.setState({ user: user });
    }

    saveSearch(e) {
        e.preventDefault();
        this.props.actions.loadUser(this.state.user);
    }

    repoRow(repo, index) {
        return (
            <div key={index}>
                <Repo key={repo.id} repo={repo} />
            </div>
        );
    }

    render() {
        return (
            <div className="container">
                <Searchbar
                    user={this.state.user}
                    onChange={this.updateSearch}
                    onSave={this.saveSearch}
                />
                {this.props.repos.map(this.repoRow)}
            </div>
        );
    }
}

HomePage.propTypes = {
    repos: PropTypes.array.isRequired,
    user: PropTypes.string.isRequired,
    actions: PropTypes.object.isRequired
};

function mapStateToProps(state) {
    return {
        repos: state.repos,
        user: state.user
    };
}

function mapDispatchToProps(dispatch) {
    return {
        actions: bindActionCreators(userActions, dispatch),
    };
}

export default connect(mapStateToProps, mapDispatchToProps)(HomePage);
Enter fullscreen mode Exit fullscreen mode

Result

Check out my Github Repo to see the other files as well. Be sure to understand basic React to utilize the power Redux in my example.

Also see a timelapse of the whole project on Youtube. According to WakaTime I spent 13 hours on the codebase and the whole recording was over 1 hour long. :)

screenshot

If you gained something from this article let me know with a comment or heart. Make sure to follow for more :)

Latest comments (1)

Collapse
 
daviducolo profile image
Davide Santangelo

great!

as my first project react, months ago I implemented a similar project, much smaller. Take a look at github.com/davidesantangelo/github....