## useFetch
> A react hook to deal with micro-queries. Also, hydrate/dehydrate them across components.
### Micro-queries
In my opinion, micro-queries is data fetching calls to a route but always included params.
###### Example
Normal query: **fetch("/safe-api/users")**
Micro query: **fetch("/safe-api/users?permission=["can_delete_everything"]")**
### **Why this is pain?**
Our fetching system currently designed with redux, redux-saga and dependency injecting. It works well with `normal queries` when we only fetch/prefetch/injecting API call dependency in a specific place or level. But now, we have options for `<Select />` components, items for `<Filters />`, to implement them, in most case, required `micro-queries`. And problems that come with `micro-queries` is:
- **Cannot effectively save in a single reducer, side-effect**
We created a `fetching combo` (action type, action, reducer, saga, adapter, selector) for fetching `/users` route. Then we have 2 other `<Select />` components, with required continuously: `/users?owned="reality_stone"`, `/users?owned="space_stone"`, Normal solution is to create more action and modify reducer just to fetch and store them separately, more work to do, more side-effect to handle, but returning is a bit of `business data` and the next problem.
- **Spamming of `fetching combos` and hard to reuse**
In other features, In term to use our previous `business data`, we have to find and inject previously created `saga`, `reducer` (which sometimes not easy work to do) repeatedly. Then desire was to call fetch function, in this level, of outer levels to use this `business data` in other places. This lead to the next problem
- **Spamming BE with same queries when navigating around FE features**
Because there fetching not exist globally, mean we have to notice about injecting them before call fetching actions, meaning the more we were injecting saga, the more fetch action we call lead to the problem that when user navigating around our app, they client keep fetching for a `micro-query` multiple time.
### **This solve the problem?**
First, it required you to create a `fetching combo` to call a query but required you to input a set of API config directly when using the hook. Instead of directly fetching when component call, I see queries from `components mount/unmount` is `fetching requests` and put them to a single queue created by `actionChannel`(a feature of redux-saga). Continuously check and dispatch them. After got response from BE, I store it on a reducer. After this, the client only refetch if the next data request can prove this data is `outdated`. So It is reducing our effort on creating `fetching combos`, help us not to care about `side-effect` when our `component mount/unmount` (even if we implement component life-cycle in the wrong way, the hooks still make sure it is not spamming our BE)
### **How to use?**
###### Normal use case
```typescript
import { useFetch } from 'utils/hooks';
import { API_CONFIG } from './constants';
import { permittedCompaniesAdapter } from './adapters';
import { PermittedCompanyRemote } from './types';
const THROTTLE = 300; // seconds
const usePermittedCompanies = (projectId: string) => {
const { data } = useFetch<{ project_contractors: PermittedCompanyRemote[] }>({
...API_CONFIG,
data: {
project_id: projectId,
},
limit: THROTTLE * 1000,
});
return permittedCompaniesAdapter(data?.project_contractors || []);
};
export default usePermittedCompanies;
```
###### Deal with pagination
```typescript
import { useState, useEffect } from 'react';
import { useFetch } from 'utils/hooks';
import { RemoteProject } from 'features/Projects/ProjectList/types';
import { API_CONFIG } from './constants';
const THROTTLE = 300; // seconds
const useProjects = (
keywords?: string,
): [RemoteProject[], boolean, () => void] => {
const [internalData, setInternalData] = useState<RemoteProject[]>([]);
const [currentPage, setCurrentPage] = useState(1);
const { data, loading } = useFetch<{
projects: RemoteProject[];
total_count: number;
total_pages: number;
}>({
...API_CONFIG,
data: {
search_keyword: !!keywords ? keywords : undefined,
page: currentPage,
},
limit: THROTTLE * 1000,
});
const refetch = () => {
setCurrentPage(1);
setInternalData([]);
};
const loadNextPage = () => {
if (currentPage < (data?.total_pages || currentPage)) {
setCurrentPage(current => current + 1);
}
};
useEffect(() => {
if (data) {
setInternalData([...internalData, ...(data.projects || [])]);
}
}, [data]);
useEffect(() => {
keywords && refetch();
}, [keywords]);
return [internalData || [], loading, loadNextPage];
};
export default useProjects;
```