Installation
pnpm add @hookraft/userequestUsage
import { useRequest } from "@hookraft/userequest"const { data, status, error, isLoading, refetch } = useRequest("/api/user", {
cacheTime: 30000,
onSuccess: (data) => console.log("Got data:", data),
onError: (err) => console.error("Failed:", err),
})Why it exists
When multiple components on the same page need the same data, most apps fire one request per component:
// Navbar.tsx
const { data: user } = useRequest("/api/user") // request 1
// Sidebar.tsx
const { data: user } = useRequest("/api/user") // request 2
// ProfileCard.tsx
const { data: user } = useRequest("/api/user") // request 3Without deduplication — 3 identical network requests fire at the same time.
With useRequest — 1 request fires. All 3 components wait for the same Promise and receive the same data when it resolves. The server never sees the duplicates.
Full Example
A dashboard where three completely separate components all need the same user data — only one network request fires.
// hooks/useUser.ts
import { useRequest } from "@hookraft/userequest"
type User = {
id: string
name: string
email: string
avatar: string
plan: "free" | "pro"
}
export function useUser() {
return useRequest<User>("/api/user", {
cacheTime: 60000, // cache for 60 seconds
onError: () => console.error("Failed to load user"),
})
}// components/Navbar.tsx
import { useUser } from "@/hooks/useUser"
export function Navbar() {
const { data: user, isLoading } = useUser()
return (
<nav>
{isLoading ? (
<div className="skeleton w-8 h-8 rounded-full" />
) : (
<img src={user?.avatar} alt={user?.name} />
)}
</nav>
)
}// components/Sidebar.tsx
import { useUser } from "@/hooks/useUser"
export function Sidebar() {
const { data: user } = useUser()
return (
<aside>
<p>{user?.name}</p>
<p>{user?.email}</p>
</aside>
)
}// components/PlanBadge.tsx
import { useUser } from "@/hooks/useUser"
export function PlanBadge() {
const { data: user } = useUser()
return (
<span>
{user?.plan === "pro" ? "Pro" : "Free"}
</span>
)
}All three components call useUser() which calls useRequest("/api/user"). Only one network request fires. When it resolves, all three components update at the same time.
Manual Mode
By default useRequest fires on mount. Set manual: true to control when it runs.
const { data, isLoading, refetch } = useRequest("/api/report", {
manual: true, // don't fetch on mount
})
// Only fires when the user clicks the button
<button onClick={refetch} disabled={isLoading}>
{isLoading ? "Loading..." : "Generate Report"}
</button>Force Refresh
refetch() always bypasses the cache and fires a fresh request — useful for pull-to-refresh or manual sync buttons.
const { data, refetch } = useRequest("/api/notifications")
<button onClick={refetch}>
Sync notifications
</button>Custom Fetcher
By default useRequest uses fetch + res.json(). Pass your own fetcher to use axios, a GraphQL client, or any async function.
import axios from "axios"
const { data } = useRequest("/api/products", {
fetcher: async (url) => {
const res = await axios.get(url)
return res.data
},
})Clearing the Cache
Use clear() to wipe the cached data for a key — useful after a mutation that changes the data on the server.
const { data: user, clear } = useRequest("/api/user")
const updateProfile = async (newName: string) => {
await fetch("/api/user", {
method: "PATCH",
body: JSON.stringify({ name: newName }),
})
// clear the cache so next render fetches fresh data
clear()
}Null Key
Pass null as the key to skip the request entirely — useful for conditional fetching.
const { data } = useRequest(isLoggedIn ? "/api/user" : null)
// request only fires when isLoggedIn is trueOptions
| Prop | Type | Default |
|---|---|---|
fetcher | (key: string) => Promise<T> | - |
cacheTime | number | 30000 |
dedupe | boolean | true |
manual | boolean | false |
onSuccess | (data: T) => void | - |
onError | (error: unknown) => void | - |
onStatusChange | (status: RequestStatus) => void | - |
Returns
| Prop | Type | Default |
|---|---|---|
data | T | undefined | - |
status | RequestStatus | - |
error | unknown | - |
isLoading | boolean | - |
isSuccess | boolean | - |
isError | boolean | - |
is(status) | (s: RequestStatus) => boolean | - |
refetch() | () => Promise<void> | - |
clear() | () => void | - |