3

I have a Custom Hook similarly with the below:

import { useEffect, useState } from 'react';

import axios from 'axios';

const myCustomHook = () => {
  const [countries, setCountries] = useState([]);
  const [isLoading, setLoading] = useState(true);

  useEffect(() => {
      (async () =>
        await axios
          .get("MY_API/countries")
          .then(response => setCountries(response.data))
          .finally(() => setLoading(false)))();
  }, []);

  return countries;
};

export default myCustomHook;

The hook works great but I am using it in three different areas of my application despite the fact that all the countries are the same wherever the hook is used.

Is there a good pattern to call the axios request just once instead of three times?

EDIT - Final Code After Solution

import { useEffect, useState } from 'react';

import axios from 'axios';

let fakeCache = {
    alreadyCalled: false,
    countries: []
};

const myCustomHook = (forceUpdate = false) => {
  const [isLoading, setLoading] = useState(true);

  if (!fakeCache.alreadyCalled || forceUpdate) {
      fakeCache.alreadyCalled = true;

      (async () =>
        await axios
          .get("MY_API/countries")
          .then(response => setCountries(response.data))
          .finally(() => setLoading(false)))();
  }

  return countries;
};

export default myCustomHook;
Dacre Denny
  • 29,664
  • 5
  • 45
  • 65
FabianoLothor
  • 2,752
  • 4
  • 25
  • 39
  • I'm voting to close this question as off-topic because it belongs to [Code Review](https://codereview.stackexchange.com). – nyedidikeke Oct 10 '19 at 04:56

2 Answers2

1

One solution to this would be to introduce a custom "cacheing layer" (between your hook and the axios request) that:

  1. caches countries data returned from the first successful request and,
  2. return the same cached data on subsequent requests

There are a number of ways this could be implemented - one possibility would be to define a getCountries() function that implements that cacheing logic in a separate module, and then call that function from your hook:

countries.js

import axios from 'axios';

// Module scoped variable that holds cache data
let cachedData = undefined;

// Example function wraps network request with cacheing layer
export const getCountries = async() => {

  // We expect the data for countries to be an array. If cachedData
  // is not an array, attempts to populate the cache with data
  if (!Array.isArray(cachedData)) {  
    const response = await axios.get("MY_API/countries");

    // Populate the cache with data returned from request
    cachedData = response.data;
  }

  return cachedData;
}

myCustomHook.js

import { useEffect, useState } from 'react';
import { getCountries } from "/countries";

const myCustomHook = () => {
  const [countries, setCountries] = useState([]);
  const [isLoading, setLoading] = useState(true);

  useEffect(() => {

    (async() => {
      try {       
        setLoading(true);

        // Update hook state with countries data (cached or fresh)
        setCountries(await getCountries());

      } finally {
        setLoading(false)
      }
    }, []);
  });
}
export default myCustomHook;
Hamza Hmem
  • 502
  • 5
  • 11
Dacre Denny
  • 29,664
  • 5
  • 45
  • 65
  • I don't like that solution 100%. It is very strange to me that a Component or a Hook changes a variable that isn't in its scope. But I can't thought in nothing better to solves the problem. Thanks. – FabianoLothor Oct 09 '19 at 23:41
0

Each instance of the component runs independently of each other.

Even though the 1st instance state has countries populated the 2nd instance is not aware of it and is still empty.

You could instead consume countries as a prop and if empty invoke the effect, otherwise just return the countries from props.

Sushanth --
  • 55,259
  • 9
  • 66
  • 105