An open API service indexing awesome lists of open source software.

https://github.com/laststance/use-get

React 19 use() + axios hooks with no long-lived cache. useGET / usePOST / usePUT / usePATCH / useDELETE.
https://github.com/laststance/use-get

axios hooks no-cache react react-19 suspense typescript use-hook vibe-coding

Last synced: 25 days ago
JSON representation

React 19 use() + axios hooks with no long-lived cache. useGET / usePOST / usePUT / usePATCH / useDELETE.

Awesome Lists containing this project

README

          

# @laststance/use-get

Tiny React 19 hooks for axios — built on `use()` + Suspense, with **no long-lived cache**.

```tsx
import { useGET } from '@laststance/use-get'

function UserCard({ id }: { id: number }) {
const user = useGET(`/api/users/${id}`)
return

{user.name}

}
```

No `queryKey`. No `invalidate`. No `staleTime`. No provider. ~2KB gzipped (excluding peer deps).

---

## When to reach for this

| You want… | Use this lib? |
| :--------------------------------------------- | :-----------: |
| Single GET in a component, Suspense-integrated | ✅ |
| A mutation (POST/PUT/PATCH/DELETE) | ✅ |
| Optimistic UI with React 19's `useOptimistic` | ✅ |
| Pagination, infinite scroll | ❌ |
| Cache invalidation across a dependency graph | ❌ |
| Background refetch on window focus | ❌ |
| DevTools with request timeline | ❌ |

For anything in the ❌ column, reach for [TanStack Query](https://tanstack.com/query) or [SWR](https://swr.vercel.app/). This lib is intentionally the **smaller** of the two — keep both in the same app if you need.

---

## Philosophy: no cache by default

TanStack Query and SWR are caches. This is not a cache. The mental model is closer to `fetch()` wrapped in Suspense.

- A `useGET` call fires a request when it mounts.
- Concurrent siblings on the same URL **share** one in-flight request (request coalescing — browsers don't dedupe concurrent requests, so forcing duplicates would just waste bandwidth).
- Once the last consumer unmounts, the entry is evicted. A remount fires a **fresh** request.
- Freshness (Cache-Control, ETag, 304) is the browser's HTTP cache's job. Service Workers can extend this further.

What you **don't** get:
- No `staleTime` or `gcTime` — nothing to tune.
- No `queryKey` — the URL + params IS the key.
- No `invalidate('users')` — there's no cache to invalidate. Unmount + remount gets you fresh data.
- No background refetch — that's a caching feature.

What you **do** get:
- Predictability. If you see a `useGET` render and don't see a request in the network tab, something is broken.
- No surprise stale renders after navigation.
- Smaller mental model: Suspense fallback → data appears → unmount → gone.

---

## Install

```bash
pnpm add @laststance/use-get axios
```

Peers: `react@^19`, `axios@^1.7`.

---

## API

### `configureClient(config)`

One-time setup. Call once at app startup. No provider needed.

```ts
import { configureClient } from '@laststance/use-get'

configureClient({
baseURL: 'https://api.example.com',
timeout: 10_000,
headers: {
Authorization: () => `Bearer ${getToken()}`, // functions re-evaluate per request
},
})
```

### `useGET(url, options?): T`

Suspends while the request is pending. Throws to the nearest Error Boundary on failure.

```ts
const user = useGET('/api/users/1')
const list = useGET('/api/posts', {
params: { page: 1, _limit: 20 },
retries: 3, // default 3 — GET is idempotent, safe to retry
timeout: 5_000, // default 10_000
})
```

**Always** wrap in `` + `` upstream.

### `usePOST` / `usePUT` / `usePATCH` / `useDELETE`

One hook per HTTP verb — method is visible at the call site instead of buried in config.

```ts
const { mutate, isPending, data, error, reset } = usePOST({
url: '/api/users',
onSuccess: (user, req) => console.log('created', user),
onError: (err, req) => console.error(err),
retries: 0, // default 0 — non-idempotent, don't retry by default
})

await mutate({ name: 'Ada' })
```

Dynamic URL:

```ts
const { mutate } = useDELETE<{ id: number }, void>({
url: (req) => `/api/users/${req.id}`,
})
```

### Error helpers

```ts
import { getStatus, isClientError, isServerError, isRetryable } from '@laststance/use-get'

if (isClientError(err)) { /* 400–499 */ }
if (isServerError(err)) { /* 500–599 */ }
if (isRetryable(err)) { /* 408, 429, 500, 502, 503, 504, ECONNABORTED */ }
```

---

## Optimistic updates (user-land)

Optimistic UI is not in this library. It's a React 19 primitive (`useOptimistic`) that you compose with the mutation hook:

```tsx
import { useGET, usePOST } from '@laststance/use-get'
import { useOptimistic, useState, useTransition } from 'react'

function Todos() {
const todos = useGET('/api/todos')
const [optimistic, addOptimistic] = useOptimistic(
todos,
(state, next: Todo) => [...state, next],
)
const { mutate } = usePOST, Todo>({ url: '/api/todos' })
const [, startTransition] = useTransition()

function add(title: string) {
startTransition(async () => {
addOptimistic({ id: -Date.now(), title, completed: false })
await mutate({ title, completed: false })
// No cache to invalidate. Next unmount/remount fetches fresh.
})
}

return

    {optimistic.map(t =>
  • {t.title}
  • )}

}
```

---

## Using it alongside TanStack Query

They coexist cleanly — they share axios (via `configureClient`) but have no other runtime overlap.

- Use **TanStack Query** for: paginated lists, infinite scroll, cross-component cache coherence, anything needing `invalidate`.
- Use **this lib** for: the one-off GET in a settings page or a dashboard tile where a `useQuery({ queryKey: ['settings'], queryFn: ... })` feels like overkill.

---

## Do / Don't

### ✅ Do

- Wrap every `useGET` tree in both `` and `` (React 19 still requires a class component for Error Boundaries).
- Call `configureClient` **once**, at module load — not inside a component.
- Let concurrent siblings on the same URL share one request (that's coalescing working as intended).
- Keep `usePOST` `retries: 0` unless you're sure the endpoint is idempotent.

### ❌ Don't

- Don't call `useGET` conditionally, inside loops, or after early returns. React's `use()` has the same rules as other hooks, plus more (see [React docs on `use`](https://react.dev/reference/react/use#usage)).
- Don't expect the library to dedupe requests **after** unmount. A remount of the same URL = a new request, by design.
- Don't try to mutate the returned data. The Promise result is shared across concurrent consumers — treat it as immutable.
- Don't use this in RSC (Server Components). For server-rendered data, `await axios.get(url)` directly — you don't need Suspense integration there.

---

## Running the example

```bash
pnpm install
pnpm build
pnpm --filter vite-spa-example dev
```

Open http://localhost:5173. The example proxies `/api/*` to [JSONPlaceholder](https://jsonplaceholder.typicode.com/) so you can play with three demos (basic GET, URL-change, optimistic POST) without standing up a backend.

---

## Testing

```bash
pnpm test # Vitest Browser Mode (Playwright chromium)
pnpm typecheck
```

Real-browser tests — `use()` and Suspense behave differently in JSDOM, and the stakes of getting this wrong are too high to mock.

---

## License

MIT © Laststance.io