Why

When we create a Single Page Application (SPA), we often need to integrate APIs. Sometimes third-party APIs, but at least our own back-ends to get the data we need to display. These APIs are based on the HTTP or WebSocket protocol, each having its requirements for connection setup and tear down.

In this article, I explain the basic integration of HTTP APIs.

What

HTTP is a stateless protocol. It’s the simplest way to get data from the server.

  • call the fetch function
  • get a promise
  • wait until the promise resolves
  • update the application with the new data

Sometimes HTTP requests fail, and sometimes we cancel them because the data hasn’t arrived yet but isn’t needed anymore.

Lifecycle Methods

Lifecycle methods are component methods with special names that are called by React to specific events.

For example, componentDidMount will be called after React rendered a component into the DOM.

Hooks

Hooks are a new part of React and allow us to do things we did with lifecycle methods, but without the need to create a component class, they work with function components only.

For example, the callback supplied to the useEffect hook function will be called every time React rendered a component.

How

First, let us integrate via lifecycle methods.

Lifecycle Methods

To use lifecycle methods, we need to create a class component that has three methods, render, componentDidMount and componentWillUnmount.

class MyComponent extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      loading: true,
      data: null,
      error: null
    };
  }

  async componentDidMount() {
    this.abortController = new AbortController();

    try {
      const response = await fetch(API_URL, {
        signal: this.abortController.signal
      });

      if (response.status >= 300)
        throw new Error(response.statusText);

      const data = await response.json();

      this.setState({ loading: false, data });
    } catch (e) {
      if (e.name != "AbortError") this.setState({ error: e.message });
    }
  }

  componentWillUnmount() {
    this.abortController.abort();
  }

  render() {
    const { data, error, loading } = this.state;

    if (!!error) return <h2>{error}</h2>;

    if (loading) return <h2>Loading...</h2>;

    return <h2>{data}</h2>;
  }
}

Let’s go through this step-by-step.

1 - Define the state in the constructor

For an HTTP request, we need three states. loading, data and error.

2 - Start the request in the componentDidMount lifecycle method

We use an asynchronous function here, so we can handle the promises of the fetch function with await.

First, we need to define an AbortController that allows us to cancel the HTTP request. Then we call fetch in a try block and await its response.

We also pass the signal of the abortController into the fetch call to wire up the controller with the request. This signal is used to cancel the request when we call the abort method of the abortController.

If the status of our request isn’t an error code, we assume the data is ready to be parsed; we add it to our state and set loading to false.

If the fetch call throws an error, we get an error code from the server, or the abort method of the abortController is called, we catch the error and render an error.

3 - Cancel the request in componentWillUnmout the lifecycle method

Since we saved a reference to the abortController to this, we can use it in the componentWillUnmount method. This method is called by React right before the component gets removed from the DOM.

The call to abort leads to a rejection of the fetch promise.

In the catch block we only call the setState method if the error isn’t an AbortError because we know that React will remove our component from the DOM.

4 - Render the different states

Finally, we have to render the different states. The main logic is inside the lifecycle methods, so the render method doesn’t need much logic anymore.

Hooks

To use hooks, we have to create a functional component. In the function we have to use two hooks, useState to store our state and useEffect to handle the API call.

function MyComponent() {
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  const [data, setData] = useState(null);

  useEffect(async () => {
    const abortController = new AbortController();

    try {
      const response = await fetch(API_URL, {
        signal: abortController.signal
      });

      if (response.status >= 300)
        throw new Error(response.statusText);

      const data = await response.json();

      setData(data);
      setLoading(false);
    } catch (e) {
      if (e.name != "AbortError") setError(e.message);
    }

    return () => abortController.abort();
  }, []);

  if (!!error) return <h2>{error}</h2>;

  if (loading) return <h2>Loading...</h2>;

  return <h2>{data}</h2>;
}

1 - First setup the state with the useState hook

The useState hook takes an initial value and returns a new state-variable and a setter function. Every time the setter is called, it will cause React to re-render the component with the new value inside the state-variable.

2 - Start the request with the useEffect hook

The useEffect hook takes a callback that is called every time React renders the component (i.e. when we call a setter function).

When we pass an empty array as the second argument to useEffect the callback is only executed after the first render. This allows us to emulate the behavior of the componentDidMount lifecycle method.

The logic in the callback is mostly the same as in the lifecycle method example. The main differences are the missing this, because we don’t have a class component and that we use the setters of the useState hooks.

3 - Cancel the request with the returned function

The function we return from the callback supplied to the useEffect hook is executed before the component is removed from the DOM. This allows us to emulate the behavior of the componentWillUnmout lifecycle method.

We call the abort method of the abortController and are done.

4 - Render the different states

To render we can use the state-variables returned by the useState hooks. Most of the logic is inside the callback we passed to useEffect so not much to do here.

API Analytics & Monitoring

Btw, if you curious about how to add API analytics to your React SPA, check out this example.

Conclusion

The two ways to integrate API calls into React components are mostly a matter of taste. Some people prefer an object-oriented approach; others want to be more functional. React lets you go either way, both allow for error handling and cancelation.

Moesif is the most advanced API Analytics platform, supporting REST, GraphQL and more. Thousands of API developers process billions of API calls through Moesif for debugging, monitoring and discovering insights.

Learn More

Leave a comment