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.
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>
)
}
}
}
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.
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 !!!