DEV Community

Cover image for useRequest: Create a Basic Declarative React hook for Managing API Requests as an Alternative to useQuery
Viktor Shcheglov
Viktor Shcheglov

Posted on • Edited on

useRequest: Create a Basic Declarative React hook for Managing API Requests as an Alternative to useQuery

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.

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
};
Enter fullscreen mode Exit fullscreen mode

2. Fetching Data:

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

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]);
Enter fullscreen mode Exit fullscreen mode

3. Caching Mechanism:

Implement a basic caching mechanism to store and retrieve responses using a global object.

const cache = {};

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

4. Adding Cache Control:

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

const clearCache = useCallback(() => { /*...*/ }, [queryKey]);
const clearAllCache = useCallback(() => { /*...*/ }, []);
Enter fullscreen mode Exit fullscreen mode

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.

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

  // Render your component based on the data, loading, and error states
};
Enter fullscreen mode Exit fullscreen mode

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:

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
};
Enter fullscreen mode Exit fullscreen mode

Define a return type for the hook that includes the generic type T:

type UseRequestReturn<T> = {
  data: T | null;
  error: Error | null;
  isLoading: boolean;
  refetch: () => void;
};
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (0)