useRequest: Create a Basic Declarative React hook for Managing API Requests as an Alternative to useQuery
Published: 2024-01-20 09:15:24

In the world of React, managing API requests and their state can be a complex task. Libraries like **react-query** have provided robust solutions, but sometimes, you might need something simpler and more controllable. This is where a custom hook, **useRequest**, comes into play. This article will guide you through creating **useRequest** - a straightforward, declarative hook for handling API requests without the advanced features of **react-query**, making it a lighter and more straightforward alternative.

**What is useRequest?**

**useRequest** is a custom React hook designed to fetch data from an API and manage its state within a component. It's built using React's basic hooks - _useState, useEffect_, and _useCallback_. The goal is to provide a simple way to make API requests, track loading states, handle errors, and cache responses with minimal setup.

**Why useRequest?**

While react-query is powerful, it can be overkill for simple projects or when you need more direct control over request handling. useRequest:

Simplifies state management for API requests.
Provides built-in loading and error states.
Offers basic caching capabilities.
Is easy to integrate and use in small to medium-sized projects.
Building the useRequest Hook:

**1. Setup and State Management:**

Start by setting up the hook structure using useState to manage the data, error, and loading states.

```javascript
const useRequest = (queryFn, { queryKey } = {}) => {
  const [data, setData] = useState(null);
  const [error, setError] = useState(null);
  const [isLoading, setIsLoading] = useState(false);

  // Rest of the logic will go here
};
```

**2. Fetching Data:**

Use useEffect and useCallback to fetch data from the API. useCallback ensures that the function doesn't get recreated unless necessary.

```javascript
const fetchData = useCallback(async () => {
  setIsLoading(true);
  try {
    const result = await queryFn();
    setData(result);
  } catch (err) {
    setError(err);
  } finally {
    setIsLoading(false);
  }
}, [queryFn, queryKey]);

useEffect(() => {
  fetchData();
}, [fetchData]);
```

**3. Caching Mechanism:**

Implement a basic caching mechanism to store and retrieve responses using a global object.
```javascript
const cache = {};

// Inside fetchData
if (cache[queryKey]) {
  setData(cache[queryKey]);
  return;
}
// Save to cache after fetching
cache[queryKey] = result;
```

**4. Adding Cache Control:**

Provide functions to clear specific cache entries or the entire cache.

```javascript
const clearCache = useCallback(() => { /*...*/ }, [queryKey]);
const clearAllCache = useCallback(() => { /*...*/ }, []);
```

**5. Return from the Hook:**

Finally, return the data, loading state, error, and cache control functions from the hook.


**Usage Example: **

Demonstrate how to use useRequest in a component, including fetching data, displaying it, and handling loading and error states.

```javascript
const MyComponent = () => {
  const { data, error, isLoading, refetch } = useRequest(fetchMyData, { queryKey: 'myData' });

  // Render your component based on the data, loading, and error states
};
```

**Bonus: Adding Type Safety to useRequest with TypeScript**

For developers using TypeScript, adding type safety to your useRequest hook enhances its reliability and the overall developer experience. TypeScript's generics allow you to define the type of data your API request will return, ensuring type correctness throughout your component.

To add type safety to useRequest, modify the hook to accept a generic type parameter. This parameter will represent the type of data expected from the API:

```javascript
const useRequest = <T,>(queryFn: () => Promise<T>, { queryKey } = {}): UseRequestReturn<T> => {
  const [data, setData] = useState<T | null>(null);
  const [error, setError] = useState<Error | null>(null);
  const [isLoading, setIsLoading] = useState(false);

  // ... rest of the hook implementation
};
```
Define a return type for the hook that includes the generic type T:

```javascript
type UseRequestReturn<T> = {
  data: T | null;
  error: Error | null;
  isLoading: boolean;
  refetch: () => void;
};
```
Now, when you use the useRequest hook in your components, you can specify the type of data you expect.



**Conclusion:**

**useRequest** provides a simpler, more direct way to handle API requests in React applications. While it doesn't offer the advanced features of react-query, it's an excellent tool for developers who need a lightweight and straightforward approach to managing API states. This custom hook can be a valuable addition to your React toolkit, especially in projects where simplicity and direct control are paramount.


**Next Steps:**

Encourage readers to try implementing **useRequest** in their projects, and suggest experimenting with additional features like request deduplication or more advanced caching mechanisms to further enhance the hook's capabilities.