Skip to content

Instantly share code, notes, and snippets.

@philipszdavido
Created February 15, 2019 15:25
Show Gist options
  • Save philipszdavido/fea2e353805294f374b6cb121ce43fa5 to your computer and use it in GitHub Desktop.
Save philipszdavido/fea2e353805294f374b6cb121ce43fa5 to your computer and use it in GitHub Desktop.

Making HTTPS request in React

There are sometime in our app where we will want to render data from an API endpoint.

Angular has a built-in HTTP module that lets devs to easily make HTTP requests in an Angular app.

import { Http, HttpModule } from '@angular/http'

@Component({
    selector:'httpC',
    template: `
        <div>
            <button (click)="fetchUsers">Fetch Users</button>
        </div>
    `
})
export class HttpComponent {
    constructor(private http: Http) {}
    fetchUsers(): any {
        return this.http.get('api/users').subscribe((res)=>{
            return res.data
        })
    }
}

@NgModule({
    declarations: [HttpComponent]
    imports: [HttpModule]
})
export class AppModule {}

You see, we simply import the HttpModule and reference it in the imports section. Then, to use Http we import the Http service and inject it in the HttpComponent class. So from there, we can perform an HTTP request from anywhere in the component.

React has no such built-in HTTP feature, so how do we access network in a React app?

In this post, we will see how to make HTTPS requests in React.

AJAX and React

There are many HTTP libraries we can use to fetch data from a endpoint:

These are the most popular HTTP libraries, we will talk about in detail below.

Ajax calls in React are made in the componentDidMount lifecycle method. It is not a must but it is a best practice. This is so because at the instantiation of a component its JSX markup is not yet in the DOM. React first creates an instance of a component, calls componentWillMount and mount its JSX markup in the DOM by calling its render method. After this is successful, it will call componentDidMount to tell you that the mounting of the component in the DOM is successful, so you can add a code to execute at this point.

We know that the data gotten from an API endpoint is used to update our component in the DOM, so naturally, it is the best bet to do it when the component is already on the DOM. componentDidMount provides us that opportunity to render our data from a network.

fetch

Fetch is a native browser API for making HTTP requests, we can use it in our React component to fetch data over a network:

class UsersComponent extends React.Component {

    constructor() {
        this.state = {
            users: []
        }
    }

    componentDidMount() {
        fetch(`/api/users`)
            .then(res => res.json())
            .then(result => this.setState({ users: result.users }))
    }

    render() {
        return (
            <div>
                Users: {this.state.users}            
            </div>
        )
    }
}

This component renders a list of users from an endpoint api/users. This endpoint will return data like this:

{
    "users": [
        {
            name: "nnamdi",
            age: 27
        },
        {
            name: "big boss",
            age: 49
        }
    ]
}

fetch('api/user') call performs a HTTPS GET requests to api/users the above json data is returned. The returned data is encapsulated in a Promise. We resolved the Promise to get the users array in the json, the result of the Promise resolution is used to update the state using this.setState(...). The state is updated in the component's render JSx markup.

Now, if we load the component we will briefly see only Users: before seeing the fetched data rendered. That brief moment before seeing the fetched data, the user will think the app is stuck. So in order stave off that, we will add logic to show some text to tell the user that something is loading.

class UsersComponent extends React.Component {

    constructor() {
        this.state = {
            users: [],
            done: false
        }
    }

    componentDidMount() {
        fetch(`/api/users`)
            .then(res => res.json())
            .then(result => this.setState({ users: result.users, done: true }))
    }

    render() {
        if(!this.state.done) {
            return (
                <div>
                    Users Loading 
                </div>
            )
        } else {
            return (
                <div>
                    Users: {this.state.users}            
                </div>
            )
        }
    }
}

We added a done state to tell us when the data is loaded in the users state. Then we added a logic in the render method, if the done state is true, that means the data has been loaded in the users state, therefore we render the users data if the done state is false we show a markup showing USer Loading to tell the user that something is happening in the background.

In the fetch(...) call we also set the done to true we the data is successfully fetched.

axios

Using axios, we will first install the library:

npm i axios -S

Then, import axios in the component:

import axios from 'axios'

class UsersComponent extends React.Component {

    constructor() {
        this.state = {
            users: [],
            done: false
        }
    }

    componentDidMount() {
        axios.get(`/api/users`)
            .then(response => response.json())
            .then(json => this.setState({ users: json.data, done: true }))
    }

    render() {
        if(!this.state.done) {
            return (
                <div>
                    Users Loading 
                </div>
            )
        } else {
            return (
                <div>
                    Users: {this.state.users}            
                </div>
            )
        }
    }
}

We convert the above component to use axios.

superagent

To use superagent, we will import the library:

npm i superagent -S

Then, we import the library in our component:

import request from 'superagent'

class UsersComponent extends React.Component {

    constructor() {
        this.state = {
            users: [],
            done: false
        }
    }

    componentDidMount() {
        request
            .get('api/users')
            .then(res => this.setState({users: res.json(), done: true}))
            .catch(err => log(err))
    }

    render() {
        if(!this.state.done) {
            return (
                <div>
                    Users Loading 
                </div>
            )
        } else {
            return (
                <div>
                    Users: {this.state.users}            
                </div>
            )
        }
    }
}

XMLHttpRequest

We can use the good ol' XMLHttpRequest to access network:

class UsersComponent extends React.Component {

    constructor() {
        this.state = {
            users: [],
            done: false
        }
    }

    componentDidMount() {
        cont xhr = new XMLHttpRequest()
        xhr.open('GET', '/api/users', true);

        xhr.onload = () => {
            if (request.status >= 200 && request.status < 400) {
                this.setState({users: xhr.responseText, done: true})
            } 
        };

        xhr.send();
    }

    render() {
        if(!this.state.done) {
            return (
                <div>
                    Users Loading 
                </div>
            )
        } else {
            return (
                <div>
                    Users: {this.state.users}            
                </div>
            )
        }
    }
}

setState Problem

In the above examples, we fetched the users data over the network in the componentDidMount lifecycle method and updated the state in the callbacks.

This could cause problems. What if before the data resolves that the component has been unmounted? Any reference to methods and properties to the case would be undefined. In our case, this.setState would not be available. This is because Promises is delegated run in an event loop. Once the JS engine hit a Promise it moves the Promise code to an Event Loop and continues with the code. So the component may be removed and its DOm dismounted but its Promise code is still running in the Event loop when the Promise code resolves the JS engine will found out that the callback in the Promise has lost reference because the class is no longer in memory and has lost reference.

var f = function name() {}

// moved to event loop
var p =new Promise(()=>{
    f()
})

// funftion f removed
delete f

// p resolves
p.then(...)

When p.then(...) eventually runs, Promise p will hit a reference error because f has been deleted from memory before it resolved.

To abort silently and make our code not to throw reference errors, we need to cancel the HTTP request. The HTTP request returns a Promise and in the resolution, we update the state, so if we cancel the HTTP request the Promise won't resolve thereby not calling any member methods or properties.

Where and when do we need to cancel HTTP requests? How do we know when our component will be unmounting?

We need to cancel the HTTP request when the component dismounts. React provides with a lifecycle that lets us run some code before a component is to be dismounted. That is, when a component is designated for removal from the DOM, a hook is called to signal the user that the component is to be dismounted from the DOM. This hook is componentWillUnMount.

class ReactComponent extends React.Component {
    // ...
    componentWillUnMount() {
        // ...
    }
}

We will run our cancellation task here.

axios

Using axios we will modify our component like this:

class UsersComponent extends React.Component {

    constructor() {
        this.state = {
            users: [],
            done: false
        }
        this.cancel = null
    }

    componentDidMount() {
        axios.get(`/api/users`, {
            cancelToken: new axios.CancelToken(function executor(c) {
                this.cancel = c
            })
        })
            .then(response => response.json())
            .then(json => this.setState({ users: json.data, done: true }))
    }

    componentWillUnMount() {
        this.cancel()
    }

    render() {
        if(!this.state.done) {
            return (
                <div>
                    Users Loading 
                </div>
            )
        } else {
            return (
                <div>
                    Users: {this.state.users}            
                </div>
            )
        }
    }
}

We added the componentWillUnMount method, then we created a cancel token. In the componentWillUnMount, we call the cancel() function it simply aborts the HTTP operation.

XMLHttpRequest

class UsersComponent extends React.Component {

    constructor() {
        this.state = {
            users: [],
            done: false
        }
        this.xhr = null
    }

    componentDidMount() {
        this.xhr = new XMLHttpRequest()
        this.xhr.open('GET', '/api/users', true);

        this.xhr.onload = () => {
            if (request.status >= 200 && request.status < 400) {
                this.setState({users: xhr.responseText, done: true})
            } 
        };

        this.xhr.send();
    }

    componentWillUnmount() {
        // Cancel the xhr request, so the callback is never called
        if (this.xhr && this.xhr.readyState != 4) {
            this.xhr.abort();
        }
    }

    render() {
        if(!this.state.done) {
            return (
                <div>
                    Users Loading 
                </div>
            )
        } else {
            return (
                <div>
                    Users: {this.state.users}            
                </div>
            )
        }
    }
}

Here, we made xhr globally so we can access in the componentWillUnMount method. There we call this.abort() this tells XMLHttpRequest to abort or stop the HTTP requests.

Conclusion

In this post, we saw how we can make HTTPS calls using different HTTPS libraries and XMLHttpRequest.

If you have any question regarding this or anything I should add, correct or remove, feel free to comment, email or DM me

Thanks !!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment