Lazy loading some data, that you also want to save into your app data store is a common enough task to warrent abstracting to a common pattern.
The goal of this pattern is to make interfacing with data from endpoints have a consistent feel as well as taking advantage of lazy loading content, and not re-fetching if the data alreay exists in your data store. Here an minimal example app I made to demo the pattern.
So how would we write a hook using this pattern, lets say the end goal is ta make a custom hook called `useTabData`
const {
data,
loading,
error
} = useTabData(tab)
It will fetch data depending on what tab the user has open, following this pattern we should always return an object with data, loading and error so our component can deal with these situations. And now the logic internal to this hook is basically going to check if the data is in the store and only make a async request if it's not there. Here's an example.
import { useState, useEffect } from "react";
import { useStore } from "./store";
export const useTabData = tab => {
const [state, dispatch] = useStore();
const coolData = state && state.coolData && state.coolData[tab];
const shouldFetchData = tab && !(coolData && coolData.length);
useEffect(() => {
if (shouldFetchData) fetchData(tab);
}, [tab, shouldFetchData]);
return {
data: coolData || [],
loading: isLoading,
error: ""
};
};
Basically we are getting our state from our store, and if there is no data for the current tab, we'll skip running `fetchData` inside the useEffect. Of course returning data, loading and error. Filling this in a little more by defining fetchData.
export const useTabData = tab => {
const [isLoading, setIsLoading] = useState(true);
const [state, dispatch] = useStore();
const coolData = state && state.coolData && state.coolData[tab];
const shouldFetchData = tab && !(coolData && coolData.length);
const fetchData = async curretTab => {
setIsLoading(true);
const promise = new Promise(resolve => {
setTimeout(
() => resolve(tab === "sweet"
? ["a", "b", "c", "d"]
: ["x", "y", "z"]),
3000
);
});
const data = await promise;
dispatch({
type: "UPDATE_COOL_DATA",
payload: {
type: currentTab,
data
}
});
setIsLoading(false);
};
useEffect(() => {
if (shouldFetchData) fetchData(tab);
}, [tab, shouldFetchData]);
return {/* data, loading, error etc*/};
};
So were using another bit of state at the top of our hook for isLoading, setting that to true at the stare of a call to fetchData, then we make our async call (mocked here with a timeout), once the data comes back we'll update the store and set isLoading to false. All is well with our frondend 🤗.
The above example can be seen here, Or codesandbox (styles arn't working in the sandbox 🤷♀️).
The Repo can be found here, (note, that it's writen in typescript).
Here's a blog post detailing a react hooks data-store similar to the one that I'm using in the example. (though the type of store doesn't matter)