'React: How optimize a custom hook that shares data?
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;
Solution 1:[1]
One solution to this would be to introduce a custom "cacheing layer" (between your hook and the axios request) that:
- caches
countries
data returned from the first successful request and, - 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;
Solution 2:[2]
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.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Hamza Hmem |
Solution 2 | Sushanth -- |