MichalKononenko/OmicronClient

View on GitHub
src/containers/http_request.js

Summary

Maintainability
A
3 hrs
Test Coverage
/**
 * Created by Michal on 2016-02-16.
 * Contains a window meant to work as a demonstration of asynchronous
 * XHR, as well as the capability of the react-redux architecture to handle
 * Asynchronicity. Hopefully, this code can serve as a design pattern for
 * designing code involving asynchronous thunk execution.
 */
'use strict';
import React, { PropTypes } from 'react';
import Header from './header';
import clone from '../object_cloning';
import reducer from '../reducer';
import {connect} from 'react-redux';
import store from '../store';
import {URLEntryForm, ResultsBox} from '../components/http_request';
import axios from 'axios';

/**
 * Template for running the asynchronous HTTP request test.
 *
 * @param {function} on_url_change The callback to execute when the url typed
 *  into the input box is changed
 * @param {function} on_button_click The callback to execute when the submit
 *  button is clicked, signifying that the user wants to access the site.
 * @param {Object} http_test_result The result of the JSON API call. This will
 *  get stringified and displayed
 * @param {string} url_value The URL of the resource to access.
 */
export const HTTPTestTemplate = ({
    on_url_change, on_button_click, http_test_result, url_value
    }) => (
    <div>
        <Header />
        <div className="container-fluid">
            <div className="row">
                <URLEntryForm url_value={url_value}
                              on_url_change={on_url_change}
                              on_button_click={on_button_click}
                />
            </div>
            <div className="row">
                <ResultsBox url_value={url_value}
                            test_result={http_test_result}
                />
            </div>
        </div>
    </div>
);

HTTPTestTemplate.propTypes = {
    on_url_change: PropTypes.func.isRequired,
    on_button_click: PropTypes.func.isRequired
};

/**
 * Takes in the current application state, and returns an object with the
 * required properties to render the HTTP Request form. Used in the connect
 * decorator to link up the application's state with the template props.
 *
 * @param {Object} state The current application state.
 * @returns {{http_test_result: (initial_state.http_test.reactjs.data|{}), url_value: *}}
 */
export function map_state_to_props(state) {
    return ({
        http_test_result: state.http_test.reactjs.data,
        url_value: state.http_test.url
    })
}

/**
 * Maps the required action creators to the callback properties required to
 * render the HTTP Request form
 * @param {function} dispatch The dispatch method for the application's store.
 *  This function is the method to which actions will be dispatched for the
 *  store's reducers to process.
 */
export const map_dispatch_to_props = (dispatch) => (
    {
        on_url_change: (event) => {
            dispatch(url_changed(event.target.value))
        },
        on_button_click: () => {
            dispatch(fetch_data())
        }
    }
);

/**
 * Connect the HTTP template and the template's mappers to the application
 * state, and export it.
 */
const HTTPTest = connect(map_state_to_props, map_dispatch_to_props)(
    HTTPTestTemplate
);

export default HTTPTest;


/**
 * Action signifying that the URL entered into the URL Test input box has
 * changed.
 * @type {string}
 */
export const URL_CHANGED = 'URL_CHANGED';

/**
 * Action creator for the URL_CHANGED action
 * @param new_url
 * @returns {{type: string, new_url: *}}
 */
export function url_changed(new_url) {
    return({
        type: URL_CHANGED,
        new_url: new_url
    })
}

/**
 * If the action is of the "URL_CHANGED" type, copies the current application
 * state, alters the url to be the new url, and returns the new state
 *
 * @param {Object} state The initial application state
 * @param {Object} action The action that is to change the state
 * @returns {Object} The new application state
 */
export function url_changed_reducer(state, action) {
    if (action.type === URL_CHANGED) {
        let new_state = clone(state);
        new_state.http_test.url = action.new_url;
        return new_state;
    } else {
        return state;
    }
}

reducer.register(url_changed_reducer);

/**
 * Toggle the fetching flag on the front end, in order to let the UI know that
 * the appropriate loading graphics (i.e. spinners) should be loaded.
 */
export const RUN_TEST = "RUN_TEST";

/**
 * Create the test action
 *
 * @returns {{type: string}}
 */
export function run_test() {
    return ({
        type: RUN_TEST
    })
}

/**
 * Toggle the fetching flag to "true"
 *
 * @param {Object} state The initial application state
 * @param {Object} action The action that is to change the state
 * @returns {Object} The new application state
 */
export function run_test_reducer(state, action) {
    if (action.type === RUN_TEST) {
        let new_state = clone(state);
        new_state.http_test.frontend.is_fetching = true;
        return new_state;
    } else {
        return state;
    }
}

reducer.register(run_test_reducer);

/**
 * Now that it's time to get a request, we dispatch the request action.
 * A separate action is done for this, in order to decouple the UI's
 * "RUN_TEST" action from the actual request.
 */
export const GET_DATA_FROM_URL = 'GET_DATA_FROM_URL';

export function get_data_from_url(url){
    return {
        type: GET_DATA_FROM_URL,
        url: url
    }
}

export function get_data_from_url_reducer(state, action) {
    if (action.type === GET_DATA_FROM_URL) {
        let new_state = clone(state);
        new_state.http_test.reactjs.is_fetching = true;
        return new_state;
    } else {
        return state;
    }
}

reducer.register(get_data_from_url_reducer);

/**
 * This action is called when the request is recieved by the UX.
 * It will write the data into the state
 */
export const RECEIVE_DATA_FROM_URL = 'RECEIVE_DATA_FROM_URL';

export function receive_data_from_url(url, received_json) {
    return {
        type: RECEIVE_DATA_FROM_URL,
        url: url,
        received_at: Date.now(),
        data: received_json
    }
}

export function receive_data_from_url_reducer(state, action) {
    if (action.type === RECEIVE_DATA_FROM_URL){
        let new_state = clone(state);

        new_state.http_test.reactjs.is_fetching = false;
        new_state.http_test.reactjs.last_updated = action.received_at;
        new_state.http_test.reactjs.data = action.data;

        new_state.http_test.frontend.is_fetching = false;
        new_state.http_test.frontend.data = action.data;

        return new_state;
    } else {
        return state;
    }
}

reducer.register(receive_data_from_url_reducer);

/**
 * Thunk action to fetch the data from the URL.
 * This is used just like any other action
 * creator.
 * @returns {Function}
 */
export function fetch_data() {
    return function(dispatch) {
        /**
         * Dispatch the action stating that the back end is getting data
         */
        let url = store.getState().http_test.url;
        dispatch(get_data_from_url(url));

        let request = axios(
            {
                url: url,
                method: "GET",
                headers: {"content-type": "application/json"}
        });

        let success_handler = (response) => (
            dispatch(receive_data_from_url(url, response.data))
        );

        return request.then(success_handler)
    }
}