7.5 KiB
Data Fetching Hooks
Last Updated: November 30, 2025
Recommendation: Consider React Query for new work
The hooks in this folder provide compact, easy-to-read utilities for data fetching, but they lack advanced features (robust caching, cache invalidation, complex retries). For new features or larger refactors, consider TanStack Query (React Query) to get better caching, devtools, and mutation handling.
Overview
This directory contains two custom hooks for making network requests to API routes:
useQuery- GET requests with loading states and retriesuseRequest- POST/PUT/DELETE requests with flexible configuration
useQuery
useQuery is a data fetching hook for making GET requests to the API with built-in error handling, loading states, and retries.
Features
- Automatic GET requests with query string parameter handling
- Loading state management
- Basic error handling with structured error objects
- Automatic retries with exponential backoff on failure
- Request cancellation on component unmount (prevents memory leaks)
- Session ID tracking via custom header (
X-Session-Id) for request tracing
Usage
import useQuery from "@/shared/hooks/useQuery";
function MyComponent() {
const { data, error, isLoading, refetch } = useQuery(
"/endpoint",
{
param1: "value1",
param2: "value2",
},
{
enabled: true,
refetchBackoffMax: 10000,
refetchBackoff: 1000,
refetchBackoffExponent: 1.5,
refetchBackoffMaxAttempts: 3,
}
);
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<div>
<button onClick={refetch}>Refresh Data</button>
{/* Render data */}
</div>
);
}
Error handling
The error object is a structured object (e.g. { message, status, data }). Use error.message for user-friendly messages and inspect error.status/error.data for debugging.
Session ID tracking
Both hooks attach an X-Session-Id header (generated once per page load and stored in sessionStorage) to help correlate frontend requests with backend logs. This is added in the shared Axios instance via an interceptor.
Parameters
url(string): The API endpoint to fetch data fromparams(object, optional): Query parameters to send with the requestconfig(object, optional): Configuration options:enabled(boolean): Whether the query should automatically run (default:true)refetchBackoff(number): Initial backoff time in ms (default:1000)refetchBackoffMax(number): Maximum backoff time in ms (default:10000)refetchBackoffExponent(number): Backoff exponential factor (default:1.5)refetchBackoffMaxAttempts(number): Maximum retry attempts (default:3)
Return Values
data: The response data (null until loaded)error: Any error that occurred during fetching (null if no error)isLoading: Boolean indicating if the request is in progressrefetch: Function to manually trigger the request again
useRequest
useRequest is a flexible hook for making non-GET HTTP requests (POST, PUT, DELETE, etc.) to the API. Returns a function that can be called multiple times.
Features
- Support for all HTTP methods (POST, PUT, DELETE, PATCH)
- Customizable request configuration (headers, auth, etc.)
- Structured error handling with status codes
- Session ID tracking (same as
useQuery) - Promise-based API for async/await usage
Usage
import { useRequest } from "@/shared/hooks/useRequest";
function MyComponent() {
const makeRequest = useRequest();
const handleSubmit = async (formData) => {
try {
const response = await makeRequest("/endpoint", formData, {
method: "POST",
headers: {
"Custom-Header": "value",
},
});
// Handle successful response
console.log(response.data);
} catch (error) {
// Handle error
console.error(error.message);
}
};
return (
<form onSubmit={handleSubmit}>
{/* Form inputs */}
<button type="submit">Submit</button>
</form>
);
}
Parameters
- The hook returns a
makeRequestfunction that accepts:url(string): The API endpoint (relative or absolute)body(object, optional): The request body/payload (will be JSON stringified)config(object, optional): Axios request configurationmethod(string): HTTP method (default: 'POST')headers(object): Custom request headerstimeout(number): Request timeout in milliseconds- Any other valid Axios request options
Return Values
- Returns a Promise that resolves to the Axios response object:
response.data- Response bodyresponse.status- HTTP status coderesponse.headers- Response headers
Abort Controller Example
For long-running requests, you can implement cancellation:
import { useRequest } from "@/shared/hooks/useRequest";
import { useEffect, useRef } from "react";
function MyComponent() {
const makeRequest = useRequest();
const abortControllerRef = useRef(null);
const handleSubmit = async (formData) => {
// Cancel previous request if still running
if (abortControllerRef.current) {
abortControllerRef.current.abort();
}
abortControllerRef.current = new AbortController();
try {
const response = await makeRequest("/api/process", formData, {
method: "POST",
signal: abortControllerRef.current.signal,
});
console.log("Success:", response.data);
} catch (error) {
if (error.name === "AbortError") {
console.log("Request cancelled");
} else {
console.error("Error:", error.message);
}
}
};
useEffect(() => {
// Cleanup: cancel request on unmount
return () => {
if (abortControllerRef.current) {
abortControllerRef.current.abort();
}
};
}, []);
return <button onClick={handleSubmit}>Submit</button>;
}
Implementation Details
Axios Configuration
Both hooks use a shared Axios instance configured in axios-config.js:
- Base URL: Automatically uses the current domain (no hardcoded URLs)
- Content-Type: Set to
application/jsonby default - Interceptors: Request interceptor adds
X-Session-Idheader - Error handling: Response interceptor structures error objects
Base URL Behavior
// Relative URLs resolve to current domain:
await makeRequest("/api/users", data); // → https://current-domain.com/api/users
// Absolute URLs work as expected:
await makeRequest("https://external-api.com/data", data); // → https://external-api.com/data
Migration note
If you decide to migrate to React Query, focus on replacing useQuery call-sites with useQuery/useMutation from @tanstack/react-query and centralizing side-effects through query keys and invalidations. The hooks here show the minimal surface you'll need to map over.
Best Practices
- Use
useQueryfor GET requests: It handles loading states and retries automatically - Use
useRequestfor mutations: POST/PUT/DELETE operations that change data - Handle errors explicitly: Always check for
errorbefore rendering data - Set
enabled: falsefor conditional queries: Prevents automatic fetching - Cancel requests on unmount: Already handled by
useQuery, implement manually foruseRequestif needed - Consider React Query for new features: Better caching, invalidation, and state management