Controlling React API Calls With Hooks
Tags: react, javascript, tutorial, api
Published: 2022-03-05 17:25:47

I love React.  But there are some aspects of the framework that have previously given me fits.  One of those aspects is in the careful controlling of API calls from within a React app.  

How many times have you witnessed this scenario?

You load a React app in the browser and, because you're a frontend developer, you find yourself opening the Inspector Tools and looking at the API (asynchronous) calls that are made from the app.  That's when you notice something... _fishy_.

The app makes a simple `GET` request to some endpoint for some batch of basic data.  Often, that data looks like it's the _kind of_ data that rarely (if ever) changes.  And yet... the app is making two, or three, or _more_(!) calls _to the exact same endpoint_.  And in each of those calls, it's retrieving _the exact same data_.

Nearly every time I witness this, I know _exactly_ why it's happening:  Because the developers didn't understand how to properly _control_ the API calls being launched from their own app!

To be fair, this is an extremely common fault in _many_ React apps that I see.  And it's so common for one very basic reason:  React does a very poor job of guiding devs on how to make _imperative_ calls.  To put it more succinctly, React tends to gloss over the problem that arises when you need to perform a single operation, at a very specific _time_, and to ensure that this operation occurs _ONLY ONCE_.  

By default, React doesn't really want you to think in imperative terms.  It constantly pushes you to program in a _declarative_ fashion.  And to be clear, that's normally a very _good_ thing.  But there are SOME things that just don't fit cleanly into a declarative model - and an API call is definitely one of those scenarios.

This drives me nuts.  Because there are certain API calls that really should only be performed _once_ (or... under very specific conditions).  So I deem it to be an act of "performance malpractice" when an app repeatedly calls for the same data - often before the user has had _any_ opportunity to even interact with the data in _any_ way.
<br/><br/>



![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y5307h43l2g4wxs80077.png) 
## Apollo Nightmares
Before I get into my solution, I wanna say a quick word about Apollo.  This seems to be the "default" package that most devs reach for when they're managing GraphQL calls.  And that's... ok.  But IMHO, it has a _major_ downfall:  All of its default documentation tries to drive you to build your API calls _declaratively_.  And for many different data calls, this is borderline-silly.  (I wrote an entire article about this.  You can read it here: https://dev.to/bytebodger/react-s-odd-obsession-with-declarative-syntax-4k8h)

Full Disclosure: It's entirely possible to manage your Apollo GraphQL calls _imperatively_.  But you gotta spend a lot of time digging around their docs to figure out how to get it right.  And this drives me crazy.

React's rendering cycle (driven by the _reconciliation process_) typically feels very "black box" to most devs.  Even for a seasoned React dev, it can be difficult to say exactly _when_ the render cycle will be invoked.  And this is why I despise Apollo's default approach.  Because API calls are definitely one aspect of your app that you should never be blindly handing over to the inner workings of React's reconciliation process.  (I wrote an entire article about the reconciliation process.  You can read it here: https://dev.to/bytebodger/react-s-render-doesn-t-render-1jc5)

So I'm not telling you to scrap Apollo (with its preferred _declarative_ syntax).  But if you're reading the rest of this tutorial and wondering, "Why don't you just use Apollo?"  This is why.  When I'm writing a responsive, asynchronous application, I've _never_ found it satisfactory to simply surrender _all_ of my API calls to the vagaries of the rendering cycle.
<br/><br/>


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/92y276vi63hz0wh0l8vy.png)
## Just Use Saga
I'm pretty much on record as being a Redux Curmudgeon.  (You can read my complete rant on the subject here: https://dev.to/bytebodger/the-splintering-effects-of-redux-3b4j)  But I fully understand that many React shops are already thoroughly ensconced in Redux.  So if your project already uses Redux, then I can safely say that you should be using Saga to manage your API calls.  It's specifically designed to handle "side effects" and the first side effects it illustrates - right on its homepage - are API calls.

So if you're already well-versed with Redux Saga, I doubt I'm going to show you anything here that will trump that bit of entrenched technology.  Use it.  It's pretty cool.

But what if you're not already a "Redux shop"?  And what if you don't wanna introduce all of Redux's built-in overhead just so you can cleanly manage a handful of API calls?  Well... there's good news.  You can do this quite simply with Hooks.
<br/><br/>



![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8tmo6nchop84el8nqix3.jpg)
## Forbidden Knowledge
OK... so I've said that this is "simple".  But that doesn't necessarily mean that it's _obvious_.  In fact, a few years ago I spent a great deal of time on the interwebs trying to figure out how to properly manage my API calls _without_ invoking the demon that is Redux.  

Sounds like a simple task, yeah?  But strangely enough, the more I searched for the solution, the more exasperated I became with the solutions that I saw proposed on various sites and blogs.  So I'm going to walk you through exactly how _I_ manage API calls whenever I'm given the freedom to choose my own approach.
<br/><br/>



![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1x12vbfbdo5bcuse971l.jpeg)
## The Basic Setup
(Before I begin, you can see all this code, live-and-working, here: https://stackblitz.com/edit/react-px4ukm)

We're gonna start with a dead-simple React app structured like so:

```
/public
/src
  /common
    /functions
      get.axios.js
      load.shared.hooks.js
    /hooks
      use.reservations.endpoint.js
    /objects
      use.js
  App.js
  index.js
  Reservations.js
  UI.js
  package.json
```

Obviously, you don't have to use _my_ file structure.  Rearrange as you see fit.  This demo is built with `create-react-app`.  Again, you obviously don't need to use that.  This can be done in a custom Webpack build just fine.  I'm going to start at the top of the app and just walk you through any pertinent points.

**package.json**
```
{
  "name": "react",
  "version": "0.0.0",
  "private": true,
  "dependencies": {
    "@toolz/use-constructor": "^1.0.1",
    "axios": "0.26.0",
    "react": "17.0.2",
    "react-dom": "17.0.2"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test --env=jsdom",
    "eject": "react-scripts eject"
  },
  "devDependencies": {
    "react-scripts": "latest"
  }
}
```

Pretty standard stuff here.  I'll only point out two features:

1. I'm using my custom `@toolz/use-constructor` NPM package.  (You can read all about it here: https://dev.to/bytebodger/constructors-in-functional-components-with-hooks-280m)  You can write the functionality for this package out manually if you like.  It just ensures that we can invoke an API call under a "traditional" constructor-like mindset - meaning that the code will run once _and only once_.  That's where we're going to call our API.

1. I'm using the `Axios` package to invoke asynchronous calls.  You can use any approach that works for you - even if you're just doing "old-skool", plain-ol'-JavaScript asynchronous calls.

**index.js**

Move along folks.  Nothing to see here.  This is just the default `index.js` file that you get when you spin up a new Create React App.  All it really does is invoke `<App/>`.

**App.js**
```javascript
import React from 'react';
import { loadSharedHooks } from './common/functions/load.shared.hooks';
import { UI } from './UI';

export default function App() {
  loadSharedHooks();

  return <UI/>;
}
```

I typically put almost _no_ real "logic" in `App.js`.  It merely serves as a launching point for the real app.  In this component, I'm just calling `<UI/>`, and... I'm calling `loadSharedHooks()`.  Here I'm using an approach that allows me to truly _share_ global state between any/all components using nothing more than core React with Hooks.  No Redux.  No other third-party shared state package.  Just... React Hooks.  (You can read all about this approach in this article: https://dev.to/bytebodger/hacking-react-hooks-shared-global-state-553b)

**/common/functions/load.shared.hooks.js**
```javascript
import { use } from '../objects/use';
import { useReservationsEndpoint } from '../hooks/use.reservations.endpoint';

export const loadSharedHooks = () => {
  use.reservationsEndpoint = useReservationsEndpoint();
};
```

This is a dead-simple function.  First, I create a custom Hook _for every endpoint_ that I'll be hitting.  And then I place a single _instance_ (a "singleton") of that endpoint into the `use` object.  This places the API calls _outside_ the standard React reconciliation process.  It allows me to control, with pinpoint accuracy, _when_ any particular API call fires.  It also allows me to then access the values from those APIs across all other components in the app.

It's important that I'm calling `loadSharedHooks()` right at the "top" of the app.  By calling it there, I ensure that any endpoints I've loaded with `loadSharedHooks()` are readily available to me wherever/whenever I need them during the application's execution.

Wondering what's inside that `use` object?  It looks like this:

**/common/objects/use.js**

```javascript
export const use = {};
```

That's it.  That's the entire `use.js` file.  It's just a plain ol' JavaScript object.  The key is that, by invoking it at the top of the application, I can then reference the values inside `use` anywhere/anytime that I want.  In this case, the Hook that manages the _endpoint_ I'm hitting will be saved into `use`.

**/common/hooks/use.reservations.endpoint.js**
```javascript
import { getAxios } from '../functions/get.axios';
import { useState } from 'react';

export const useReservationsEndpoint = () => {
  const [reservations, setReservations] = useState([]);
  const axios = getAxios();

  const loadReservations = async () => {
    const response = await axios.call(
      'GET',
      'https://cove-coding-challenge-api.herokuapp.com/reservations'
    );
    if (response.status === 200) setReservations(response.data);
  };

  return {
    loadReservations,
    reservations,
  };
};
```

This code manages the single endpoint that we're using for this demo.  The _actual_ call is handled in `loadReservations()`.  It leverages my custom `axios` wrapper.  (I'm not going to outline the `axios` wrapper here.  You can peruse it in the StackBlitz demo if you like.  If this were a "full" app, I'd have functions inside the `axios` wrapper for `POST`, `PUT`, and `PATCH` operations.  But for this simple demo, the wrapper only contains code for a `GET` call.)

Notice in this endpoint Hook that I only return the values for `loadReservation` and `reservations`.  `reservations` contains the data that's returned from the endpoint.  `loadReservations()` allows us to invoke the `GET` operation without needing to write out the full asynchronous code within the body of our components.  `setReservations` is _not_ returned.  This keeps the downstream components from trying to update the endpoint values directly, without utilizing this custom Hook.

**UI.js**
```javascript
import React from 'react';
import { useConstructor } from '@toolz/use-constructor';
import { use } from './common/objects/use';
import { Reservations } from './Reservations';

export const UI = () => {
  useConstructor(() => use.reservationsEndpoint.loadReservations());

  return <Reservations/>;
};
```
`<UI/>` doesn't do _much_.  On the surface, it just seems to call `<Reservations/>`.  But there's one critical feature here:  It leverages `useConstructor()` to load, once (and _only_ once), the `loadReservations()` call.  This ensures that we're not loading the reservations endpoint every time the app performs a re-render.  Once that's been accomplished, it simply renders `<Reservations/>`.

**Reservations.js**
```javascript
import React, { useState } from 'react';
import { use } from './common/objects/use';

export const Reservations = () => {
  const [index, setIndex] = useState(0);
  const reservationsEndpoint = use.reservationsEndpoint;

  const displayCurrentReservation = () => {
    if (reservationsEndpoint.reservations.length === 0)
      return null;
    const reservation = reservationsEndpoint.reservations[index];  
    return <>
      <br/>
      <div>
        Room Name: {reservation.room.name}
        <br/>
        Start Datetime: {reservation.start}
        <br/>
        End Datetime: {reservation.end}
      </div>  
      <br/>
    </>
  }

  const displayNextButton = () => {
    if (reservationsEndpoint.reservations.length === 0 || index ===  reservationsEndpoint.reservations.length - 1)
      return null;
    return <>
      <button onClick={() => setIndex(index + 1)}>
        Next
      </button>  
    </>  
  }

  const displayPreviousButton = () => {
    if (reservationsEndpoint.reservations.length === 0 || index === 0)
      return null;
    return <>
      <button 
        onClick={() => setIndex(index - 1)}
        style={{marginRight: 20}}
      >
        Previous
      </button>  
    </>  
  }

  return <>
    <div>
      {reservationsEndpoint.reservations.length} reservations found
    </div>  
    <div>
      Current showing reservation #{index}:
    </div>  
    {displayCurrentReservation()}  
    {displayPreviousButton()}
    {displayNextButton()}
  </>;
}
```

Obviously, this is the "meat" of the application.  Here's a quick synopsis of what it accomplishes:

1. It sets a state variable for `index`, so we always know which reservation we're looking at.

1. It accesses the `reservationsEndpoint` which was previously loaded with `loadSharedHooks()`.

1. It then displays the total number of reservations retrieved, the index of the current reservation, and some basic info about the reservation itself.  It also shows `Previous` and `Next` buttons that allow you to cycle forward-or-backward through the existing reservations.
<br/><br/>



![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2u1jxmjju2e11fzdm9fo.jpeg)
## Takeaways

1. If you open the Inspector Tools while viewing the StackBlitz demo, you'll see that the `GET` to the reservations endpoint is only ever called _once_.  Even when you use the `Previous` or `Next` buttons, the `GET` call is never repeated, even though the _state_ for `<Reservations/>` is updated and the component is repeatedly re-rendered.

1. This was done without _any_ third-party packages.  No Redux (or Redux _Saga_).  No Apollo.  No other third-party state-management tools.  

1. The API call is never dependent upon the React reconciliation process, meaning that we used neither the lifecycle methods inherent in class-based components, nor the confusing mess of dependencies that are spawned with `useEffect()`.

1. The _biggest_ takeaway I'd like you to embrace is that API calls should _always_ be tightly controlled.  Your app shouldn't be repeatedly calling _the same endpoint_ for _the same data_.