{"id":51130778,"url":"https://github.com/laststance/use-get","last_synced_at":"2026-06-25T12:01:29.166Z","repository":{"id":362985082,"uuid":"1218988216","full_name":"laststance/use-get","owner":"laststance","description":"React 19 use() + axios hooks with no long-lived cache. useGET / usePOST / usePUT / usePATCH / useDELETE.","archived":false,"fork":false,"pushed_at":"2026-04-23T12:19:01.000Z","size":72,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-06T22:12:17.715Z","etag":null,"topics":["axios","hooks","no-cache","react","react-19","suspense","typescript","use-hook","vibe-coding"],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/laststance.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-23T12:18:58.000Z","updated_at":"2026-04-23T12:19:17.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/laststance/use-get","commit_stats":null,"previous_names":["laststance/use-get"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/laststance/use-get","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/laststance%2Fuse-get","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/laststance%2Fuse-get/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/laststance%2Fuse-get/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/laststance%2Fuse-get/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/laststance","download_url":"https://codeload.github.com/laststance/use-get/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/laststance%2Fuse-get/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34773843,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-25T02:00:05.521Z","response_time":101,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["axios","hooks","no-cache","react","react-19","suspense","typescript","use-hook","vibe-coding"],"created_at":"2026-06-25T12:01:28.135Z","updated_at":"2026-06-25T12:01:29.135Z","avatar_url":"https://github.com/laststance.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# @laststance/use-get\n\nTiny React 19 hooks for axios — built on `use()` + Suspense, with **no long-lived cache**.\n\n```tsx\nimport { useGET } from '@laststance/use-get'\n\nfunction UserCard({ id }: { id: number }) {\n  const user = useGET\u003cUser\u003e(`/api/users/${id}`)\n  return \u003cdiv\u003e{user.name}\u003c/div\u003e\n}\n```\n\nNo `queryKey`. No `invalidate`. No `staleTime`. No provider. ~2KB gzipped (excluding peer deps).\n\n---\n\n## When to reach for this\n\n| You want…                                      | Use this lib? |\n| :--------------------------------------------- | :-----------: |\n| Single GET in a component, Suspense-integrated |      ✅       |\n| A mutation (POST/PUT/PATCH/DELETE)             |      ✅       |\n| Optimistic UI with React 19's `useOptimistic`  |      ✅       |\n| Pagination, infinite scroll                    |      ❌       |\n| Cache invalidation across a dependency graph   |      ❌       |\n| Background refetch on window focus             |      ❌       |\n| DevTools with request timeline                 |      ❌       |\n\nFor 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.\n\n---\n\n## Philosophy: no cache by default\n\nTanStack Query and SWR are caches. This is not a cache. The mental model is closer to `fetch()` wrapped in Suspense.\n\n- A `useGET` call fires a request when it mounts.\n- 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).\n- Once the last consumer unmounts, the entry is evicted. A remount fires a **fresh** request.\n- Freshness (Cache-Control, ETag, 304) is the browser's HTTP cache's job. Service Workers can extend this further.\n\nWhat you **don't** get:\n- No `staleTime` or `gcTime` — nothing to tune.\n- No `queryKey` — the URL + params IS the key.\n- No `invalidate('users')` — there's no cache to invalidate. Unmount + remount gets you fresh data.\n- No background refetch — that's a caching feature.\n\nWhat you **do** get:\n- Predictability. If you see a `useGET` render and don't see a request in the network tab, something is broken.\n- No surprise stale renders after navigation.\n- Smaller mental model: Suspense fallback → data appears → unmount → gone.\n\n---\n\n## Install\n\n```bash\npnpm add @laststance/use-get axios\n```\n\nPeers: `react@^19`, `axios@^1.7`.\n\n---\n\n## API\n\n### `configureClient(config)`\n\nOne-time setup. Call once at app startup. No provider needed.\n\n```ts\nimport { configureClient } from '@laststance/use-get'\n\nconfigureClient({\n  baseURL: 'https://api.example.com',\n  timeout: 10_000,\n  headers: {\n    Authorization: () =\u003e `Bearer ${getToken()}`, // functions re-evaluate per request\n  },\n})\n```\n\n### `useGET\u003cT\u003e(url, options?): T`\n\nSuspends while the request is pending. Throws to the nearest Error Boundary on failure.\n\n```ts\nconst user = useGET\u003cUser\u003e('/api/users/1')\nconst list = useGET\u003cPost[]\u003e('/api/posts', {\n  params: { page: 1, _limit: 20 },\n  retries: 3,      // default 3 — GET is idempotent, safe to retry\n  timeout: 5_000,  // default 10_000\n})\n```\n\n**Always** wrap in `\u003cSuspense\u003e` + `\u003cErrorBoundary\u003e` upstream.\n\n### `usePOST` / `usePUT` / `usePATCH` / `useDELETE`\n\nOne hook per HTTP verb — method is visible at the call site instead of buried in config.\n\n```ts\nconst { mutate, isPending, data, error, reset } = usePOST\u003cCreateUser, User\u003e({\n  url: '/api/users',\n  onSuccess: (user, req) =\u003e console.log('created', user),\n  onError: (err, req) =\u003e console.error(err),\n  retries: 0, // default 0 — non-idempotent, don't retry by default\n})\n\nawait mutate({ name: 'Ada' })\n```\n\nDynamic URL:\n\n```ts\nconst { mutate } = useDELETE\u003c{ id: number }, void\u003e({\n  url: (req) =\u003e `/api/users/${req.id}`,\n})\n```\n\n### Error helpers\n\n```ts\nimport { getStatus, isClientError, isServerError, isRetryable } from '@laststance/use-get'\n\nif (isClientError(err)) { /* 400–499 */ }\nif (isServerError(err)) { /* 500–599 */ }\nif (isRetryable(err))   { /* 408, 429, 500, 502, 503, 504, ECONNABORTED */ }\n```\n\n---\n\n## Optimistic updates (user-land)\n\nOptimistic UI is not in this library. It's a React 19 primitive (`useOptimistic`) that you compose with the mutation hook:\n\n```tsx\nimport { useGET, usePOST } from '@laststance/use-get'\nimport { useOptimistic, useState, useTransition } from 'react'\n\nfunction Todos() {\n  const todos = useGET\u003cTodo[]\u003e('/api/todos')\n  const [optimistic, addOptimistic] = useOptimistic(\n    todos,\n    (state, next: Todo) =\u003e [...state, next],\n  )\n  const { mutate } = usePOST\u003cPartial\u003cTodo\u003e, Todo\u003e({ url: '/api/todos' })\n  const [, startTransition] = useTransition()\n\n  function add(title: string) {\n    startTransition(async () =\u003e {\n      addOptimistic({ id: -Date.now(), title, completed: false })\n      await mutate({ title, completed: false })\n      // No cache to invalidate. Next unmount/remount fetches fresh.\n    })\n  }\n\n  return \u003cul\u003e{optimistic.map(t =\u003e \u003cli key={t.id}\u003e{t.title}\u003c/li\u003e)}\u003c/ul\u003e\n}\n```\n\n---\n\n## Using it alongside TanStack Query\n\nThey coexist cleanly — they share axios (via `configureClient`) but have no other runtime overlap.\n\n- Use **TanStack Query** for: paginated lists, infinite scroll, cross-component cache coherence, anything needing `invalidate`.\n- 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.\n\n---\n\n## Do / Don't\n\n### ✅ Do\n\n- Wrap every `useGET` tree in both `\u003cSuspense\u003e` and `\u003cErrorBoundary\u003e` (React 19 still requires a class component for Error Boundaries).\n- Call `configureClient` **once**, at module load — not inside a component.\n- Let concurrent siblings on the same URL share one request (that's coalescing working as intended).\n- Keep `usePOST` `retries: 0` unless you're sure the endpoint is idempotent.\n\n### ❌ Don't\n\n- 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)).\n- Don't expect the library to dedupe requests **after** unmount. A remount of the same URL = a new request, by design.\n- Don't try to mutate the returned data. The Promise result is shared across concurrent consumers — treat it as immutable.\n- Don't use this in RSC (Server Components). For server-rendered data, `await axios.get(url)` directly — you don't need Suspense integration there.\n\n---\n\n## Running the example\n\n```bash\npnpm install\npnpm build\npnpm --filter vite-spa-example dev\n```\n\nOpen 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.\n\n---\n\n## Testing\n\n```bash\npnpm test         # Vitest Browser Mode (Playwright chromium)\npnpm typecheck\n```\n\nReal-browser tests — `use()` and Suspense behave differently in JSDOM, and the stakes of getting this wrong are too high to mock.\n\n---\n\n## License\n\nMIT © Laststance.io\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flaststance%2Fuse-get","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flaststance%2Fuse-get","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flaststance%2Fuse-get/lists"}