A lightweight HTTP library built on top of TanStack React Query, providing reusable request metadata, strongly typed hooks, automatic authentication support, and a clean developer experience for React and React Native applications.
- 🚀 Built on top of TanStack React Query
- 📦 Lightweight with zero state management dependency
- 🎯 Strongly typed with TypeScript
- 🔐 Supports Bearer & Basic Authentication
- 🔄 Automatic query caching
- ⚡ Automatic retry and background refetch
- 📱 Works with React and React Native
- 🔌 Framework agnostic (Jotai, Redux, Zustand, Context...)
- 🌳 Tree-shakable
- ❤️ Re-export TanStack React Query APIs
Install the package.
yarn add use-react-httpor
npm install use-react-httpInstall peer dependencies.
yarn add react @tanstack/react-queryTo maintain a clean and scalable codebase, structure your project by separating API metadata definitions from custom state-injected wrapper hooks:
src/
├── services/ # Shared API layer
│ ├── client/
│ │ ├── queryClient.ts # QueryClient configuration
│ │ ├── useHttpQuery.ts # Auth-injected wrapper for queries
│ │ └── useHttpCommand.ts # Auth-injected wrapper for mutations
│ ├── requests/ # HTTP Request Metadata declarations
│ │ ├── auth.request.ts
│ │ ├── appointment.request.ts
│ │ └── customer.request.ts
│ └── types/ # Shared DTOs and Data Interfaces
│ └── appointment.ts
├── store/ # State management (Jotai, Zustand, etc.)
│ └── auth.store.ts
├── features/ # UI Components / Feature Modules
│ └── appointments/
│ └── AppointmentList.tsx
└── jotai/
└── genericAtom.ts # Generic helper if using Jotai
import { QueryClient } from "use-react-http";
export const queryClient = new QueryClient({
defaultOptions: {
queries: {
retry: 1,
refetchOnWindowFocus: false,
},
},
});Wrap your application with QueryClientProvider.
import React from "react";
import ReactDOM from "react-dom/client";
import { QueryClientProvider } from "use-react-http";
import { queryClient } from "@/service/client/queryClient";
import App from "./App";
ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<QueryClientProvider client="{queryClient}">
<App/>
</QueryClientProvider>
</React.StrictMode>
);The setup is exactly the same.
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>Each API endpoint should be defined once using createHttpRequestMeta.
import {
httpUtil,
HttpRequestData,
PaginationResponse,
PaginationRequest
} from "use-react-http";
interface GetAllAppointmentSettingRequest
extends HttpRequestData {
readonly query: PaginationRequest;
}
export interface AppointmentSetting {
id: string;
}
const baseUrlApp = "https://your-domain.xyz";
export const getAllAppointmentSetting =
httpUtil.createHttpRequestMeta<
GetAllAppointmentSettingRequest,
PaginationResponse<AppointmentSetting>
>({
baseUrl: baseUrlApp,
path: "/api/v1/appointment-settings",
method: "GET",
authentication: "bearer"
});use-react-http does not manage authentication.
Each application should create a wrapper hook to inject the access token.
Example using Jotai.
Setup Jotai
yarn add jotaiimport { atom, PrimitiveAtom } from 'jotai';
export function genericAtom<T>(initialValue: T): PrimitiveAtom<T> & { init: T; } {
return atom(initialValue) as PrimitiveAtom<T> & { init: T; };
}import { genericAtom } from '@/jotai';
export interface AuthState {
readonly token: string;
readonly expiresAt?: number;
readonly idToken?: string;
readonly refreshToken?: string;
}
export const authState = genericAtom<AuthState | null>(null);Custom useCustomHttpQuery
import { useAtomValue } from "jotai";
import { authState } from "@/store/auth.store";
import {
useHttpQuery as useBaseHttpQuery,
HttpRequestMeta,
useHttpQuery,
HttpQueryOptions
} from "use-react-http";
export function useHttpQuery<
TRequest,
TResponse
>(
requestMeta: HttpRequestMeta<TRequest, TResponse>,
requestData?: HttpRequestData,
options?: HttpQueryOptions<TResponse>
) {
const auth = useAtomValue(authState);
return useBaseHttpQuery(
requestMeta,
requestData,
options,
auth?.token
);
}You only need to create this wrapper once.
Fetching appointment settings.
const {
data,
isLoading,
error
} = useHttpQuery(
getAllAppointmentSetting
);The returned object is exactly the same as TanStack React Query's useQuery.
const {
data
} = useHttpQuery(
getAllAppointmentSetting,
{
query: {
pageNumber: 1,
pageSize: 20
}
}
);const settings = data?.items;
settings?.forEach(setting => {
console.log(setting.timeSlotDuration);
});Disable cache by enabling noCaching.
const query = useHttpQuery(
getAllAppointmentSetting,
undefined,
{
noCaching: true
}
);All TanStack React Query options are supported.
const query = useHttpQuery(
getAllAppointmentSetting,
undefined,
{
enabled: true,
retry: false,
staleTime: 1000 * 60,
gcTime: 1000 * 60 * 5,
refetchOnWindowFocus: false,
}
);Everything from @tanstack/react-query is re-exported.
import {
QueryClient,
QueryClientProvider,
useQueryClient,
useInfiniteQuery,
useIsFetching,
useMutationState,
} from "use-react-http";No need to install or import directly from @tanstack/react-query.
Mutations are used to create, update, or delete data.
Like useCustomHttpQuery, create a wrapper once to inject the access token automatically.
Example using Jotai.
import { useAtomValue } from "jotai";
import { authState } from "@/store/auth.store";
import {
useHttpCommand as useBaseHttpCommand,
HttpRequestMeta,
HttpCommandOptions
} from "use-react-http";
export function useHttpCommand<
TRequest,
TResponse
>(
requestMeta : HttpRequestMeta<TRequest, TResponse>,
options?: HttpCommandOptions<TRequest, TResponse>
) {
const auth = useAtomValue(authState);
return useBaseHttpCommand(
requestMeta,
options,
auth?.token
);
}import { httpUtil } from "use-react-http";
const baseUrlApp = "https://your-domain.xyz";
export interface CreateAppointmentRequest {
body: {
customerId: string;
employeeId: string;
serviceId: string;
startTime: string;
};
}
export interface CreateAppointmentResponse {
id: string;
}
export const createAppointment =
httpUtil.createHttpRequestMeta<
CreateAppointmentRequest,
CreateAppointmentResponse
>({
baseUrl: baseUrlApp,
path: "/api/v1/appointments",
method: "POST",
authentication: "bearer"
});const createAppointmentCommand =
useHttpCommand(
createAppointment
);await createAppointmentCommand.mutateAsync({
body: {
customerId: "customer-id",
employeeId: "employee-id",
serviceId: "service-id",
startTime: new Date().toISOString()
}
});createAppointmentCommand.mutate({
body: {
customerId: "1",
employeeId: "2",
serviceId: "3",
startTime: new Date().toISOString()
}
});const response =
await createAppointmentCommand.mutateAsync({
body: {
customerId: "1",
employeeId: "2",
serviceId: "3",
startTime: new Date().toISOString()
}
});
console.log(response.id);You can use all TanStack React Query mutation callbacks.
const command =
useHttpCommand(
createAppointment,
{
onSuccess(data) {
console.log(data);
},
onError(error) {
console.error(error);
},
onSettled() {
console.log("Completed");
}
}
);Refresh cached data after a successful mutation.
import {
useQueryClient
} from "use-react-http";
const queryClient = useQueryClient();
const command =
useHttpCommand(
createAppointment,
{
onSuccess() {
queryClient.invalidateQueries({
queryKey: [
"appointments"
]
});
}
}
);export const updateAppointment =
httpUtil.createHttpRequestMeta({
baseUrl: () => getEnv().MANAGEMENT_SERVER,
path: "/api/v1/appointments/:id",
method: "PUT",
authentication: "bearer"
});const updateCommand =
useHttpCommand(
updateAppointment
);
await updateCommand.mutateAsync({
pathData: {
id: appointmentId
},
body: {
employeeId: "employee-id"
}
});export const deleteAppointment =
httpUtil.createHttpRequestMeta({
baseUrl: () => getEnv().MANAGEMENT_SERVER,
path: "/api/v1/appointments/:id",
method: "DELETE",
authentication: "bearer"
});const deleteCommand =
useHttpCommand(
deleteAppointment
);
await deleteCommand.mutateAsync({
pathData: {
id: appointmentId
}
});useHttpCommand returns the same object as TanStack React Query's useMutation.
const {
mutate,
mutateAsync,
data,
error,
status,
isPending,
isSuccess,
isError,
isIdle,
reset
} = useHttpCommand(
createAppointment
);HTTP errors are automatically thrown as HttpRequestError.
try {
await command.mutateAsync({
body: {
...
}
});
}
catch (error) {
console.log(error);
}If the server returns 401 Unauthorized, the hook automatically redirects to:
/error/unauthorized
You can customize this behavior in your application if needed.
For endpoints requiring authentication, simply set:
authentication: "bearer"The access token will be injected automatically by your custom wrapper.
const command =
useHttpCommand(
createAppointment
);No need to manually set the Authorization header for every request.
Basic authentication is also supported.
authentication: "basic"Provide the Basic token through the request header.
await command.mutateAsync({
header: {
basic: basicToken
}
});Define each API endpoint once.
requests/
│
├── appointmentRequest.ts
├── customerRequest.ts
├── employeeRequest.ts
└── authRequest.ts
Then reuse them everywhere.
const query =
useHttpQuery(
getAppointments
);
const command =
useHttpCommand(
createAppointment
);This approach keeps your application strongly typed, reusable, and easy to maintain.
use-react-http works seamlessly with React Native.
The only requirement is wrapping your application with QueryClientProvider.
import {
QueryClient,
QueryClientProvider
} from "use-react-http";
const queryClient = new QueryClient();
export default function App() {
return (
<QueryClientProvider client={queryClient}>
<RootNavigator />
</QueryClientProvider>
);
}Example using AsyncStorage.
import AsyncStorage from "@react-native-async-storage/async-storage";
import { useHttpQuery } from "use-react-http";
export function useHttpQuery(
requestMeta,
requestData?,
options?
) {
const token = useAuthToken();
return useHttpQuery(
requestMeta,
requestData,
options,
token
);
}Everything else works exactly the same as React.
Pagination is supported through request parameters.
const { data } = useHttpQuery(
getAllAppointmentSetting,
{
query: {
pageNumber: 1,
pageSize: 20
}
}
);Every request automatically generates a unique query key.
The generated key contains:
[
method,
baseUrl,
path,
query,
pathData
]
Example
[
"GET",
"https://api.example.com",
"/appointments",
{
pageNumber:1,
pageSize:20
},
{}
]
This ensures proper caching and automatic refetching.
All APIs are fully typed.
const {
data
} = useHttpQuery(
getAllAppointmentSetting
);TypeScript automatically infers
PaginationResponse<AppointmentSetting>No generic parameters are required.
useHttpQuery(
requestMeta,
requestData?,
options?,
assignToken?
)| Parameter | Description |
|---|---|
| requestMeta | Request metadata created by createHttpRequestMeta |
| requestData | Query, body, path and header data |
| options | TanStack React Query options |
| assignToken | Bearer access token |
useHttpCommand(
requestMeta,
options?,
assignToken?
)| Parameter | Description |
|---|---|
| requestMeta | Request metadata |
| options | TanStack Mutation options |
| assignToken | Bearer access token |
interface HttpRequestData {
body?;
query?;
pathData?;
header?;
}createHttpRequestMeta({
baseUrl,
path,
method,
authentication
});Everything from @tanstack/react-query is re-exported.
import {
QueryClient,
QueryClientProvider,
useQueryClient,
useInfiniteQuery,
useQueries,
useMutation,
useQuery,
useIsFetching,
useIsMutating,
dehydrate,
hydrate,
} from "use-react-http";requests/
├── authRequest.ts
├── appointmentRequest.ts
├── customerRequest.ts
├── employeeRequest.ts
└── invoiceRequest.ts
hooks/
├── useCustomHttpQuery.ts
└── useCustomHttpCommand.ts
Inject authentication once.
Reuse everywhere.
const {
data
} = useHttpQuery(
getCustomers
);
const createCustomer =
useHttpCommand(
createCustomerRequest
);Avoid creating HttpClient manually inside components.
Each endpoint should only be declared once.
export const getCustomers =
httpUtil.createHttpRequestMeta({
...
});Reuse throughout the application.
No.
Authentication is injected by your application through wrapper hooks.
No.
No.
No.
Yes.
Yes.
Yes.
This library is only a lightweight wrapper and re-exports all APIs from TanStack React Query.
-
✅ useHttpQuery
-
✅ useHttpCommand
-
✅ React Query re-export
-
✅ Authentication support
-
✅ React Native support
-
⏳ Request interceptor
-
⏳ Response interceptor
-
⏳ File Upload
-
⏳ File Download
-
⏳ Refresh Token
-
⏳ Global Configuration
-
⏳ Retry Policy
Contributions are welcome.
Feel free to submit issues or pull requests.
MIT License
Copyright (c) 2026 Thanh Se