{"id":26246386,"url":"https://github.com/mittwald/react-use-promise","last_synced_at":"2025-07-01T04:34:13.572Z","repository":{"id":176518007,"uuid":"650495501","full_name":"mittwald/react-use-promise","owner":"mittwald","description":"Simple and declarative use of Promises in your React components. Observe their state and refresh them in various advanced ways.","archived":false,"fork":false,"pushed_at":"2025-06-18T14:23:26.000Z","size":384690,"stargazers_count":9,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-06-18T15:30:39.475Z","etag":null,"topics":["api","async","data","fetch","hooks","promise","react","suspense"],"latest_commit_sha":null,"homepage":"","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/mittwald.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}},"created_at":"2023-06-07T07:34:29.000Z","updated_at":"2025-06-18T14:20:28.000Z","dependencies_parsed_at":"2023-09-26T17:26:01.265Z","dependency_job_id":"6cc5bbc5-1c76-4d4d-8312-99331fe1e78c","html_url":"https://github.com/mittwald/react-use-promise","commit_stats":{"total_commits":186,"total_committers":4,"mean_commits":46.5,"dds":0.3978494623655914,"last_synced_commit":"601983d702ac1a6957406dd6051535c7d695ef1f"},"previous_names":["mittwald/react-use-promise"],"tags_count":50,"template":false,"template_full_name":null,"purl":"pkg:github/mittwald/react-use-promise","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mittwald%2Freact-use-promise","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mittwald%2Freact-use-promise/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mittwald%2Freact-use-promise/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mittwald%2Freact-use-promise/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mittwald","download_url":"https://codeload.github.com/mittwald/react-use-promise/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mittwald%2Freact-use-promise/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":261250276,"owners_count":23130541,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["api","async","data","fetch","hooks","promise","react","suspense"],"created_at":"2025-03-13T13:18:21.917Z","updated_at":"2025-07-01T04:34:13.541Z","avatar_url":"https://github.com/mittwald.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# React Use Promise\n\nSimple and declarative use of Promises in your React components. Observe their\nstate and refresh them in various advanced ways.\n\n**Now with built-in support for 🌐 [HTTP](#-http-resources)!**\n\n```jsx\nimport { Suspense } from \"react\";\nimport { usePromise, refresh } from \"@mittwald/react-use-promise\";\n\n// Async loader 👇 function\nconst loadNewsItem = async (id) =\u003e {\n  const res = await fetch(\n    `https://hacker-news.firebaseio.com/v0/item/${id}.json`,\n  );\n  return res.json();\n};\n\nconst NewsItem = ({ id }) =\u003e {\n  const news = usePromise(loadNewsItem, [id], {\n    // ✨ Use the async loader 👆 function with its ID-parameter\n    tags: [`news/${id}`],\n    // Use tags 🏷️ with support for \"tree structures\" 🌳\n  });\n\n  // Do not care about any loading states – just use the result 🤩\n  return (\n    \u003cli\u003e\n      {news.by}: {news.title}\n    \u003c/li\u003e\n  );\n};\n\nconst App = () =\u003e {\n  const reloadAllNews = () =\u003e {\n    // ✨ Reload Promises 🔄 by using tags and glob pattern *️⃣\n    refresh({ tag: \"news/*\" });\n  };\n\n  return (\n    // The Suspense component will render a fallback️\n    // if any child component is in loading state ⏱️\n    \u003cSuspense fallback={\u003cdiv\u003eLoading...\u003c/div\u003e}\u003e\n      \u003cNewsList\u003e\n        \u003cNewsItem id={1000} /\u003e\n        \u003cNewsItem id={1001} /\u003e\n        \u003cNewsItem id={1002} /\u003e\n      \u003c/NewsList\u003e\n      \u003cbutton onClick={reloadAllNews}\u003eReload news\u003c/button\u003e\n    \u003c/Suspense\u003e\n  );\n};\n```\n\n## Features\n\n- Simple and declarative use of Promises, [loading-](#defining-loading-views)\n  and [error views](#error-handling)\n- Built-in support to load data with [HTTP resources](#-http-resources)\n- Works with data fetching via Next.js server actions\n- Auto-refresh after [timeout](#autorefresh-only-supported-by-usepromise) or on\n  [window focus](#refreshonwindowfocus-only-supported-by-usepromise)\n- Type-Safe API\n- No \"double-loading\" when using \"same\" Promise in different places in your app\n- Caching with support for [custom tags](#tags-1)\n- [Opt-out Suspense-based loading](#opt-out-suspense)\n- [Cache invalidation](#refreshing-resources) with\n  [glob support](#hierarchical-tags)\n- [Observable loading state](#watchstate)\n- Set up [lazy-loading](#-lazy-loading-with-async-resources) \"async resources\"\n  with the alternative [`getAsyncResource`-API](#getasyncresource) and pass them\n  as prop to child components\n- [\"Resourceify\"](#resourceify) any async function\n- Fully tested and well-structured TypeScript code-base\n\n## Table of Contents\n\n- [Installing](#installing)\n- [Terminology](#terminology)\n- [API](#api)\n  - [usePromise](#usepromise)\n  - [getAsyncResource](#getasyncresource)\n  - [Async resource](#async-resource-1)\n  - [Options](#options)\n  - [refresh](#refresh-1)\n- [Caching](#caching)\n  - [Challenges concerning Caching](#challenges-concerning-caching)\n  - [Resource store](#resource-store)\n  - [Creating unique storage keys](#creating-unique-storage-keys)\n  - [Using explicit loader IDs](#using-explicit-loader-ids)\n- [Tags](#tags-1)\n  - [Hierarchical Tags](#hierarchical-tags)\n- [Refreshing resources](#refreshing-resources)\n- [HTTP Resources](#-http-resources)\n  - [Request config](#request-config)\n  - [Default request config](#default-request-config)\n  - [Caching and refreshing of HTTP Resources](#caching-and-refreshing-of-http-resources)\n  - [API](#http-api)\n- [Lazy loading with Async Resources](#-lazy-loading-with-async-resources)\n- [Defining loading views](#defining-loading-views)\n- [Opt-Out Suspense](#opt-out-suspense)\n- [Opt-Out Loading](#opt-out-loading)\n- [Error handling](#error-handling)\n- [Best practices](#best-practices)\n- [Migration guides](#migration-guides)\n\n## Installing\n\nWith npm:\n\n```shell\n$ npm install @mittwald/react-use-promise\n```\n\nWith Yarn:\n\n```shell\n$ yarn add @mittwald/react-use-promise\n```\n\n## Terminology\n\n### (Async) resource\n\nAn async resource (or just resource) represents something that has to be loaded\nasynchronously. Asynchronous loading involves different states, like \"loading\"\nor \"loading is done\", that should be reflected in the UI. Basically async\nresources encapsulating Promises and equipping them with relevant features like\nobserving the Promises state or caching the result.\n\n### Async loader (function)\n\nThe async loader function is simply a function returning a Promise and thus acts\nas the basic input for async resources.\n\n## API\n\n### usePromise\n\nGet the result of a Promise by passing an async loader with the relevant loader\nparameters and an optional configuration to the `usePromise` hook.\n\n#### usePromise(asyncLoader, loaderParameters, options?)\n\nReturns: the value of the async resource\n\nFor possible options see [Options section](#options).\n\n```javascript\nimport { usePromise } from \"@mittwald/react-use-promise\";\n\n// 1. Inline loader function\nconst response = usePromise(() =\u003e axios(\"/user/12345\"), []);\n\n// 2. Loader function with parameters\nconst getUser = (id) =\u003e axios(`/user/${id}`);\nconst response = usePromise(getUser, [12345]); // 👈 loader parameters as array\n\n// 3. With options\nconst response = usePromise(getUser, [12345], {\n  autoRefresh: {\n    seconds: 30,\n  },\n});\n```\n\n### getAsyncResource\n\nThis is an alternative and more advanced API to the `usePromise` hook. For\ndetails see\n[Lazy Loading with Async Resources](#-lazy-loading-with-async-resources). The\n`usePromise` hook uses the [`getAsyncResource`-API](#getasyncresource) under the\nhood and is basically just a shortcut for\n`getAsyncResource(asyncLoader, loaderParameters, options).use()`.\n\nGet an async resource by passing an async loader with the relevant loader\nparameters and an optional configuration to the `getAsyncResource` function.\n\n#### getAsyncResource(asyncLoader, loaderParameters, options?)\n\nReturns: [Async resource](#async-resource-1)\n\nFor possible options see [Options section](#options).\n\n```javascript\nimport { getAsyncResource } from \"@mittwald/react-use-promise\";\n\n// 1. Inline loader function\nconst userResource = getAsyncResource(() =\u003e axios(`/user/123`), []);\n\n// 2. Loader function with parameters\nconst getUser = (id) =\u003e axios(`/user/${id}`);\nconst userResource = getAsyncResource(getUser, [123]);\n\n// 3. With options\nconst userResource = getAsyncResource(getUser, [123], {\n  tags: [\"api\"],\n});\n\n// 4. Usage in factory function\nconst getUserResource = (id) =\u003e getAsyncResource(getUser, [id]);\n```\n\n### Async resource\n\nThe async resource returned by `getAsyncResource` has the following API.\n\n#### .use(options?)\n\nFor possible options see [Options section](#options).\n\nReturns: the value of the async resource, or the result object when\n`useSuspense: false` (see [Opt-out Suspense](#opt-out-suspense))\n\nCalling the `use` method will actually start the loading process and returns the\nvalue of the async resource, once it is loaded. Everytime the resource is\nrefreshed, the used value automatically updates itself.\n\n```jsx\nimport getUserResource from \"../resources/user\";\n\nconst Username = ({ id }) =\u003e {\n  const user = getUserResource(id).use(); // 👈 using the resource value\n  return \u003c\u003e{user.name}\u003c/\u003e;\n};\n```\n\n#### .refresh()\n\nCalling the `refresh` method will clear the cached resource value and trigger a\nreload, if the resource is being used in any mounted component.\n\n```jsx\nimport getScoreResource from \"../resources/score\";\n\nconst Score = ({ matchId }) =\u003e {\n  const scoreResource = getScoreResource(matchId);\n  const score = scoreResource.use();\n  const reloadScore = () =\u003e scoreResource.refresh(); // 👈 refresh the resource\n\n  return (\n    \u003cScoreBox\u003e\n      \u003cScore\u003e\n        {score.home} - {score.guest}\n      \u003c/Score\u003e\n      \u003cbutton onClick={reloadScore}\u003eReload\u003c/button\u003e\n    \u003c/ScoreBox\u003e\n  );\n};\n```\n\n#### .watchState()\n\nReturns: `\"void\" | \"loading\" | \"loaded\" | \"error\"`\n\nYou can watch the resources state by calling the `watchState` method.\n\n```jsx\nimport getScoreResource from \"../resources/score\";\n\nconst Score = ({ matchId }) =\u003e {\n  const scoreResource = getScoreResource(matchId);\n  const scoreResourceState = scoreResource.watchState();\n  const score = scoreResource.use();\n  const scoreIsLoading = scoreResourceState === \"loading\";\n\n  return (\n    \u003cScoreBox\u003e\n      \u003cScore\u003e\n        {score.home} - {score.guest}\n      \u003c/Score\u003e\n      \u003cLoadingSpinner visible={scoreIsLoading} /\u003e\n    \u003c/ScoreBox\u003e\n  );\n};\n```\n\n### Options\n\nYou can configure `usePromise` and `getAsyncResource` with the following\noptions:\n\n#### autoRefresh – only supported by hooks (`use*`)\n\nType:\n[Duration like object](https://moment.github.io/luxon/api-docs/index.html#durationfromobject)\\\nDefault:\n`undefined`\n\nWhen a duration is configured, the resource will automatically be refreshed in\nthe provided interval. If the same resource has multiple auto-refresh intervals,\nthe shortest interval will be used.\n\n```javascript\nautoRefresh: {\n  seconds: 30;\n}\n```\n\n#### refreshOnWindowFocus – only supported by hooks (`use*`)\n\nType: `boolean`\\\nDefault: `false`\n\nSet this option to `true`, if the resource should automatically be refreshed, if\nthe window is re-focused.\n\n#### keepValueWhileLoading – only supported by hooks (`use*`)\n\nType: `boolean`\\\nDefault: `true`\n\nIf `true`, the previously loaded value will be returned during refresh is in\nprogress. The loading view will only be triggered during initial load.\n\nIf `false`, the loading view will always be triggered – during initial load and\nrefresh as well.\n\n#### tags\n\nType: `string[]`\\\nDefault: `undefined`\n\nWith this option you can assign tags to resources. Tags allow you to be\nexpressive and flexible when accessing resources, e.g. when you need to refresh\nmultiple resources at once.\n\nYou can find details about how to use tags in the [Tags section](#tags-1).\n\n```javascript\ntags: [\"hackernews\", \"getRequest\"];\n```\n\n#### loaderId\n\nType: `string`\\\nDefault: `\"null\"`\n\n**It is very unlikely** that you ever need to use this option, but to get around\nthe \"same code\" issue (see\n[\"Caveats of default storage key generation\"](#caveats-of-default-storage-key-generation)),\nyou can set an explicit loader ID, that identifies the loader function.\n\n#### useSuspense – only supported by hooks (`use*`)\n\nType: `boolean`\\\nDefault: `true`\n\nSet this to `false` to opt-out Suspense-based loading behavior. See\n[Opt-Out Suspense](#opt-out-suspense) for more details.\n\n### refresh\n\nIf you do not have direct access to the resource that should be refreshed, you\ncan use the global refresh method.\n\n#### refresh()\n\nRefreshes all resources.\n\n```jsx\nimport { refresh } from \"@mittwald/react-use-promise\";\n\nexport const Menu = () =\u003e (\n  \u003cMenu\u003e\n    \u003cMenuItem url=\"/dashboard\" /\u003e\n    \u003cMenuItem url=\"/projects\" /\u003e\n    \u003cMenuItem url=\"/profile\" /\u003e\n    \u003cMenuButton onClick={() =\u003e refresh()}\u003eRefresh all data\u003c/MenuButton\u003e\n  \u003c/Menu\u003e\n);\n```\n\n#### refresh(options)\n\nThe refresh method takes an option object to do a more selective refresh.\n\nThe following options are supported:\n\n- `tag` (`string`): Refreshes all resources matching the given tag. Glob pattern\n  are supported here. See also the\n  [Hierarchical Tags section](#hierarchical-tags).\n- `error` (`true | error`): Set this option to `true`, to refresh all resource\n  with errors. Set this option to an error instance, to refresh all resources\n  with a matching error. See the [Error handling](#error-handling) section for\n  more details.\n\n### resourceify\n\n`resourceify` creates a factory function for async resources based on given\nasync loader functions.\n\n#### resourceify(asyncLoader)\n\nExample of how to create factory functions for your async loaders:\n\n```js\nimport { resourceify } from \"@mittwald/react-use-promise\";\n\nconst getUser = (id) =\u003e axios(`/user/${id}`);\n// Creates a function to get user resources\nconst getUserResource = resourceify(getUser);\n// `myUser` is an async resource\nconst myUser = getUserResource([\"me\"], { refresh: { seconds: 30 } });\nmyUser.use();\n\n// Factory method for HTTP GET-Requests via Axios\nimport axios from \"axios\";\nexport const getHttpResource = resourceify(axios.get);\n```\n\n## Caching\n\nCaching the result of async loader functions is essential to make this library\nwork. Without it, components will end-up in an endless render-loop, since the\nre-render after finished loading will trigger the loader function again.\n\nTo break this loop, the result (and even the error) of the loader function is\ncached inside the resource instance. If a cached result exists, the loader must\nnot be called again and the cached value is used instead.\n\n### Challenges concerning Caching\n\nThis caching approach comes with two essential issues one has to care about:\n\n- creating unique cache keys to store results\n- providing flexible ways of cache invalidation\n\n### Resource store\n\nEvery time when `usePromise` resp. `getAsyncResource` is called, either a new\nresource is created or an existing resource is taken from the resource store. If\na resources has loaded once, it exists in the store and contains the cached\nresult of the async loader function.\n\nIt is noticeable that not the raw result is cached in some \"result cache\" – **it\nis the resource the keeps the cached result which itself is stored in the\nresource store**.\n\n### Creating unique storage keys\n\nBasically you do not have to care about storage keys at all, but there are some\nodd situations where the default storage key generation fails, and produces\nconflicting keys. If so, you probably might not get the expected resource. When\nyou are experiencing such issues, you better take a look at this section.\n\n#### Same resource, same storage key\n\nWhen creating storage keys, the same key should be formed for async resources\nconsidered as \"same\". An async resource is \"the same\" compared to another\nresource, if:\n\n- the loader function is the same\n- the parameters used to load the resource are the same\n\nExamples:\n\n```javascript\nconst getUser = (id) =\u003e axios(`/user/${id}`);\nconst getPost = (id) =\u003e axios(`/post/${id}`);\n\n// Same storage key 🍎🍎\nconst response1 = usePromise(getUser, [12345]);\nconst response2 = usePromise(getUser, [12345]);\n\n// Same storage key 🍐🍐\nconst response3 = usePromise(() =\u003e getUser(12345), []);\nconst response4 = usePromise(() =\u003e getUser(12345), []);\n\n// Different storage keys 🍎🍋\nconst response5 = usePromise(getUser, [12345]);\nconst response6 = usePromise(getUser, [56789]);\n\n// Different storage keys 🍎🍉\nconst response7 = usePromise(getUser, [12345]);\nconst response8 = usePromise(getPost, [12345]);\n\n// Different storage keys 🍎🌶️\nconst response9 = usePromise(() =\u003e getUser(12345), []);\nconst response10 = usePromise(() =\u003e getUser(12344 + 1), []);\n```\n\n#### Caveats of default storage key generation\n\nIn some odd situations the default storage key generation fails and may generate\nconflicting keys. To evaluate these situations, you should know some details\nabout the default behaviour, which is basically implemented like this:\n\n```javascript\nimport { hash } from \"object-code\";\n\nconst storageKeyObject = {\n  loaderFunction,\n  parameters,\n};\n\nconst storageKey = hash(storageKeyObject);\n```\n\nAs you can see, the implementation heavily relies on the\n[`object-code`](https://www.npmjs.com/package/object-code#getting-started)\nlibrary, which\n\n\u003e is a blazing fast hash code generator that supports every possible javascript\n\u003e value.\n\nWhen it comes to \"hashing\" a function (in this case the async loader function),\n`object-code` uses `toString()` to get the function _code_ and uses it for\nfurther hashing. This is the point where problems may arise. When two function\nusing the same code, they must not necessarily behave the same. Think of\nfunctions using variables from their parent scope (🤯).\n\nThe following example may result in inconsistencies. (But only if users and\nposts share same the IDs.)\n\n```jsx\n// File Username.jsx\nimport repo from \"./user\";\n\nconst loadUserById = (id) =\u003e repo.loadById(id);\n// same code 👆, but different repo 😧\n\nexport const Username = ({ id }) =\u003e {\n  const user = usePromise(loadUserById, [id]);\n  //👆 may be a cached post\n  return \u003c\u003e{user.name}\u003c/\u003e;\n};\n\n// File PostTitle.jsx\nimport repo from \"./post\";\n\nconst loadPostById = (id) =\u003e repo.loadById(id);\n// same code 👆, but different repo 😧\n\nexport const PostTitle = ({ id }) =\u003e {\n  const post = usePromise(loadPostById, [id]);\n  //👆 may be a cached user\n  return \u003c\u003e{post.title}\u003c/\u003e;\n};\n```\n\n### Using explicit loader IDs\n\n**It is very unlikely** that you ever need to use this option, but to get around\nthe \"same code\" issue demonstrated above, you can set explicit loader IDs in the\noptions object.\n\nThis example shows how to set explicit loader IDs:\n\n```javascript\n// ...\nconst post = usePromise(loadPostById, [id], {\n  loaderId: \"loadPostById\", // explicit loader ID 😮‍💨\n});\n// ...\n```\n\n## Tags\n\nYou can assign tags to resources. A tag is simply a string that classifies the\nresource. In advanced scenarios you might want to assign multiple tags to one\nresource, expressing the resource matches different classifications.\n\nUsing relevant tags creates the possibility to be very expressive and flexible\nwhen it comes to accessing multiple resources at once, e.g. when you need to\nrefresh them.\n\nFor example, you might use tags to refresh some specific resources, when a\nbackend event occurs.\n\n```jsx\n// File components/Chat.jsx\nimport { usePromise } from \"@mittwald/react-use-promise\";\n\nconst Chat = ({ chatId }) =\u003e {\n  const messages = usePromise(loadChatMessages, [chatId], {\n    tags: [`chat/${chatId}`],\n  });\n\n  return (\n    \u003cdiv\u003e\n      {messages.map((msg) =\u003e (\n        \u003cChatMessage message={msg} key={msg.id} /\u003e\n      ))}\n    \u003c/div\u003e\n  );\n};\n\n// File setup.js\nimport { refresh } from \"@mittwald/react-use-promise\";\n\nbackendEventListener.on(\"chatUpdated\", (chatId) =\u003e {\n  refresh({\n    tag: `chat/${chatId}`,\n  });\n});\n```\n\n### Hierarchical Tags\n\nTags are supporting a tree structure, compared to paths in filesystems or URLs\n(e.g. `chat/12345/messages`). This creates the possibility to structure your\nresources into hierarchical classes. Combined with\n[glob support](https://www.npmjs.com/package/minimatch) in cache invalidation,\nyou can invalidate resources matching a certain level in the tree.\n\n```js\nimport { refresh } from \"@mittwald/react-use-promise\";\n\nrefresh({\n  tag: `chat/12345/**/*`,\n});\n// Matches everything for the chat 12345\n// - chat/12345/messages/1\n// - chat/12345/messages/2\n// - chat/12345/metaData\n\nrefresh({\n  tag: `chat/12345/messages/*`,\n});\n// Matches all messages for the chat 12345\n// - chat/12345/messages/1\n// - chat/12345/messages/2\n```\n\n## Refreshing resources\n\nIf a resource has a cached result, the async loader function must not be called\nagain and the cached value is used instead. To invalidate the cached value you\ncan call the [`refresh()`](#refresh) method on the resource or use the global\n[`refresh()`](#refresh-1) method. As a result the async loader function will\nautomatically be called again and a component update is triggered when the\nresult is available.\n\nRefreshing resources that are not used in any mounted component, will suspend\nthe reload until the resource is actually used the next time.\n\nYou might also use the auto-refresh mechanisms\n[after timeout](#autorefresh-only-supported-by-usepromise) or\n[refocus window](#refreshonwindowfocus-only-supported-by-usepromise).\n\n```js\nimport { refresh } from \"@mittwald/react-use-promise\";\n```\n\n## 🌐 HTTP Resources\n\nA major use case of this library might be to load data from some HTTP-API. This\nis exactly why a preconfigured HTTP resource is included in this package. The\nsimplest way is to use the `useHttpData()` method to GET data from a given URL.\n\n```jsx\nimport { useHttpData } from \"@mittwald/react-use-promise/http\";\n\nconst NewsItem = ({ id }) =\u003e {\n  const news = useHttpData(\n    `https://hacker-news.firebaseio.com/v0/item/${id}.json`,\n  );\n\n  return (\n    \u003cli\u003e\n      {news.by}: {news.title}\n    \u003c/li\u003e\n  );\n};\n```\n\n**Important**: As this feature requires the `axios` package you need to include\nit in your dependencies!\n\n```shell\nyarn add axios\n```\n\n### Request config\n\nUnder the hood the popular HTTP client Axios is used to perform the request. To\nmake all [Axios config](https://axios-http.com/docs/req_config) options\navailable to you, the complete config object can be passed as the second\nparameter to all HTTP functions.\n\n```ts\nconst videoGames = useHttpData\u003cArticle[]\u003e(`/articles`, {\n  params: {\n    catagory: \"video-games\",\n  },\n});\n```\n\n### Default request config\n\nTo set the default config for each request (e.g the `baseURL` or an\n`Authorization` header) you can use the\n[`axios.defaults`](https://axios-http.com/docs/config_defaults) object.\n\n```js\nimport axios from \"axios\";\n\naxios.defaults.baseURL = \"https://api.foo.org/v2\";\n```\n\n### Caching and refreshing of HTTP Resources\n\nHTTP Resources are cached by the identity of the request config – meaning,\ndifferent request configs result in different HTTP Resources.\n\nCache tags added to HTTP Resources:\n\n- `http/method/(HTTP METHOD)`\n- `http/uri/(REQUEST URI)`\n\nThis lets you refresh HTTP Resources with a certain path.\n\n```js\nimport { refresh } from \"@mittwald/react-use-promise\";\n\nfunction refreshArticles() {\n  refresh({\n    tag: \"http/uri/**/articles\",\n  });\n}\n```\n\n### HTTP API\n\n### useHttpData(url, requestConfig?, usePromiseOptions?)\n\nFetches data from a given URL with an optional request configuration.\n\nReturns: the parsed body data of the response\n\n#### url\n\nThe URL to perform the request on\n\n#### requestConfig?\n\nThe [Axios request config](https://axios-http.com/docs/req_config)\n\n#### usePromiseOptions?\n\nFor possible options see [Options section](#options).\n\n### useHttp(url, requestConfig?, usePromiseOptions?)\n\nGet the response from a given URL with an optional request configuration.\n\nReturns: an [Axios response object](https://axios-http.com/docs/res_schema)\n\n#### url\n\nThe URL to perform the request on\n\n#### requestConfig?\n\nThe [Axios request config](https://axios-http.com/docs/req_config)\n\n#### usePromiseOptions?\n\nFor possible options see [Options section](#options).\n\n### getHttpResource(url, requestConfig?, usePromiseOptions?)\n\nCreates an Async Resource that performs an HTTP request as the loader function.\n\nReturns: an [Axios response object](https://axios-http.com/docs/res_schema)\n\n#### url\n\nThe URL to perform the request on\n\n#### requestConfig?\n\nThe [Axios request config](https://axios-http.com/docs/req_config)\n\n#### usePromiseOptions?\n\nFor possible options see [Options section](#options).\n\n## 😴 Lazy loading with Async Resources\n\nWhen constructing your React app, you might get to the point, where you must\ndecide whether to\n\n- load something from your API and pass the result into some child component,\n- or the child component should load the data itself by some ID from its props.\n\nSince loading async resources involves different loading states, it is a good\nadvice, to react on these state changes, as deep as possible in the component\ntree, to avoid needless re-renderings of parent components.\n\n### 💅 Presentational vs. 📦 Container Components\n\nThe components deep in the tree are usually some small components, like the\n`\u003cAvatar /\u003e` component showing an avatar image. These kind of components do have\na clear focus on the visual representation and should not be polluted by some\nloading state handling. They are often referred to as _Presentational\nComponents_. They can be developed as \"standalone\" and showcased in Storybooks,\nwithout the need of any API.\n\nWhen the time has come to bring your Presentational Components to life, you can\n\"wrap\" the component in a container that \"connects\" it with real data. This is\nthe job of _Container Components_. They care about loading data and compose\nPresentational Components to display the data.\n\nExample Presentational Component:\n\n```tsx\n// Props needed for presentation\ninterface Props {\n  imageUrl?: string;\n  size?: number;\n}\n\nconst Avatar: FC\u003cProps\u003e = ({ imageUrl, size }) =\u003e {\n  return \u003cImage round url={imageUrl} size={size} /\u003e;\n};\n```\n\nExample Container Component:\n\n```tsx\ninterface Props {\n  // ID is needed to load the user\n  userId: string;\n  // optional presentational props are possible\n  size?: number;\n}\n\nconst UserAvatar: FC\u003cProps\u003e = ({ userId, size }) =\u003e {\n  const profile = usePromise(api.loadUserProfile, [userId]);\n  return \u003cAvatar imageUrl={profile.avatarUrl} size={size} /\u003e;\n};\n```\n\n### Async Resources as prop\n\nAs an alternative to the user ID, you might pass an Async User Resource as prop\nto your Container Component. This makes the component more flexible, because it\nis not tied to the actual loading method.\n\nFor instance if user profiles can be loaded via multiple API methods, the\n`\u003cUserAvatar /\u003e` component can still be used without any changes.\n\nIt is noticeable that creating Async Resources in any parent component does\n**not trigger the loading process**. Therefore, the `.use()` method can be used\nin the child components, where the data is actually needed.\n\nAlternative Container Component with lazy loading:\n\n```tsx\n// ...\nimport { AsyncResource, getAsyncResource } from \"@mittwald/react-use-promise\";\n\ninterface Props {\n  // Async User Resource passed as prop\n  user: AsyncResource\u003cUserData\u003e;\n  size?: number;\n}\n\nconst UserAvatar: FC\u003cProps\u003e = ({ user, size }) =\u003e {\n  // .use() 🔔 triggers loading when needed\n  const profile = user.use();\n  return \u003cAvatar imageUrl={profile.avatarUrl} size={size} /\u003e;\n};\n\nconst UserProfileMenu: FC = () =\u003e {\n  // 😴 Creating the Async Resource does not trigger loading\n  const myProfile = getAsyncResource(api.loadMyUserProfile, []);\n\n  return (\n    \u003cMenu\u003e\n      \u003cMenuHeader\u003e\n        \u003cUserAvatar user={myProfile} size={128} /\u003e\n      \u003c/MenuHeader\u003e\n      {/* menu items*/}\n    \u003c/Menu\u003e\n  );\n};\n```\n\n## Defining loading views\n\nWhen using `@mittwald/react-use-promise` (or other Suspense-based approaches)\nyou have to define a loading boundary (Reacts `\u003cSuspense /\u003e` component) to\ndisplay the loading view. Loading boundaries are quite similar to error\nboundaries. They \"catch\" active loading processes in any child component and\nrender a fallback instead. The benefits of this pattern are:\n\n- No pollution of handling loading states over and over again in your components\n- Definition of loading views, where you need them in the component tree\n- Flexible level of loading views: Sometimes it is less distracting to use a\n  single loading view for a larger section of your app, but sometimes small\n  pieces of async data should not keep off parts of your app from rendering.\n- Declarative and easy to understand\n\nExample of how to define loading views:\n\n```jsx\nconst App = () =\u003e (\n  \u003cSuspense fallback={\u003cFullScreenLoadingSpinner /\u003e}\u003e\n    {/* ... */}\n    \u003cMainLayout\u003e\n      \u003cSuspense fallback={\u003cMainMenuLoadingSkeleton /\u003e}\u003e\n        \u003cMainMenu /\u003e\n      \u003c/Suspense\u003e\n      \u003cSuspense fallback={\u003cMainContentLoadingSkeleton /\u003e}\u003e\n        \u003cMainContent /\u003e\n      \u003c/Suspense\u003e\n    \u003c/MainLayout\u003e\n    {/* ... */}\n  \u003c/Suspense\u003e\n);\n```\n\n### Gotchas when defining \"built-in\" loading views\n\nWhen you are using `.use()` or `usePromise()` in your component, it can not\ndefine its own loading boundary – at least not for directly used Async\nResources.\n\nIn this example the fallback component will not be shown:\n\n```jsx\nconst UserAvatar = ({ userResource, size }) =\u003e {\n  const user = userResource.use();\n  // 👆 any code below this line will not be executed 🙅 until the async loader is done.\n  const loadingView = \u003cAvatarSkeleton size={size} /\u003e;\n  return (\n    \u003cSuspense fallback={loadingView}\u003e\n      {/* This fallback 👆 is not rendered for the used resource from above. 😢 */}\n      \u003cAvatar imageUrl={user.avatarUrl} size={size} /\u003e\n    \u003c/Suspense\u003e\n  );\n};\n```\n\nThe following approaches can help you to solve this issue:\n\n- Split up your component into two separate components – one private with the\n  regular rendering, and another one wrapping it with a loading boundary while\n  forwarding props to it.\n\n```jsx\nconst Component = ({ userResource, size }) =\u003e {\n  const user = userResource.use();\n  return \u003cAvatar imageUrl={user.avatarUrl} size={size} /\u003e;\n};\n\nexport const UserAvatar = (props) =\u003e {\n  const loadingView = \u003cAvatarSkeleton size={props.size} /\u003e;\n\n  return (\n    \u003cSuspense fallback={loadingView}\u003e\n      \u003cComponent {...props} /\u003e\n    \u003c/Suspense\u003e\n  );\n};\n```\n\n- Opt-out Suspense-based loading behavior.\n\n```jsx\nconst UserAvatar = ({ userResource, size }) =\u003e {\n  const user = userResource.use({ useSuspense: false });\n\n  // `user` is an object with `isSet` and `value` properties\n  if (!user.isSet) {\n    return \u003cAvatarSkeleton size={size} /\u003e;\n  }\n\n  // `value` only exists when `isSet === true`\n  return \u003cAvatar imageUrl={user.value.avatarUrl} size={size} /\u003e;\n};\n```\n\n- Use a Higher Order Component (HOC) that enhances your regular implementation\n  with a loading boundary. This HOC could optionally add an error boundary.\n\n```jsx\nconst UserAvatar = withLoadingBoundary(({ userResource, size }) =\u003e {\n  const user = userResource.use();\n  return \u003cAvatar imageUrl={user.avatarUrl} size={size} /\u003e;\n}, AvatarSkeleton);\n```\n\n- Use a render component where you access the resource:\n\n```jsx\nconst UserAvatar = ({ userResource, size }) =\u003e {\n  const loadingView = \u003cAvatarSkeleton size={size} /\u003e;\n  return (\n    \u003cSuspense fallback={loadingView}\u003e\n      \u003cRender\u003e\n        {() =\u003e {\n          const user = userResource.use();\n          return \u003cAvatar imageUrl={user.avatarUrl} size={size} /\u003e;\n        }}\n      \u003c/Render\u003e\n    \u003c/Suspense\u003e\n  );\n};\n```\n\n## Opt-Out Suspense\n\nWhen you want to react explicitly on the loading-state, the suspense-based\nloading behavior is a little bit obstructive, e.g. when you want to define\ncomponents with\n[built-in loading views](#gotchas-when-defining-built-in-loading-views).\n\nYou can opt-out Suspense by setting the `useSuspense` option to `false`. When\nSuspense is disabled, calling `.use()` resp. `usePromise()` will not trigger any\n`\u003cSuspense /\u003e` component. Instead, a result object is returned, containing\nloading information and the eventual value.\n\nExample of how the opt-out behaves:\n\n```tsx\nconst message = usePromise(loadMessage, [\"12345\"], { useSuspense: false });\n\nif (!message.hasValue) {\n  return \u003cLoadingView /\u003e;\n}\n\nreturn (\n  \u003cMessageView message={message.value} activitySpinner={message.isLoading} /\u003e\n);\n```\n\n### Return object with disabled Suspense\n\nThe object returned by `.use()` resp. `usePromise()` has the following\nproperties, when Suspense is disabled.\n\n- `isLoading`: Is `true` when the resource is loading or reloading. `false`\n  otherwise.\n- `hasValue`: Is `true` when a resource value is available, including the \"old\"\n  value when reloading. `false` otherwise.\n- `value`: Contains the resource value. Is only available if `hasValue` is\n  `true`.\n- `maybeValue`: Contains the resource value, when the resource has loaded.\n  `undefined` otherwise.\n\nExamples of result objects:\n\n```js\n// Loading the first time or reloading with keepValueWhileLoading disabled\n({\n  isLoading: true,\n  maybeValue: undefined,\n  hasValue: false,\n});\n\n// When value has loaded\n({\n  isLoading: false,\n  maybeValue: \"Foo\",\n  value: \"Foo\",\n  hasValue: true,\n});\n\n// When value is reloading\n({\n  isLoading: true,\n  maybeValue: \"Foo\",\n  value: \"Foo\",\n  hasValue: true,\n});\n```\n\n## Opt-Out Loading\n\nYou probably know that you should\n[not call hooks conditionally](https://react.dev/warnings/invalid-hook-call-warning#breaking-rules-of-hooks).\nBut what if the loader parameters you want to use e.g. in `usePromise` are\noptional properties, and the loader function requires them? In this case just\nuse `null` as parameters and loading is disabled!\n\n😈 Bad:\n\n```jsx\nconst Username = ({ id }) =\u003e {\n  if (!id) {\n    return null;\n  }\n  // 💥 This will throw a React error because `usePromise` is called conditionally!\n  const user = usePromise(loadUseProfile, [id]);\n  return \u003c\u003e{user.name}\u003c/\u003e;\n};\n```\n\n😇 Good:\n\n```jsx\nconst Username = ({ id }) =\u003e {\n  // When required loader parameters can be undefined: use null 👇\n  const user = usePromise(loadUseProfile, id ? [id] : null);\n  // ⚠️ In this case, `user` can also be `undefined`!\n  return \u003c\u003e{user ? user.name : \"Unknown\"}\u003c/\u003e;\n};\n```\n\n## Error handling\n\nErrors that occur in any async loader function can be handled with regular error\nboundaries. **With only one exception**. When you have implemented a\nretry-render-mechanism in your error fallback view, the erroneous resource has\nto be refreshed as well before re-rendering.\n\nWhen you use the popular\n[`react-error-boundary`](https://www.npmjs.com/package/react-error-boundary)\npackage, you can utilize its `onReset` property to refresh all erroneous\nresources, while resetting the error view.\n\nExample of refreshing erroneous resources on reset:\n\n```jsx\n// ...\nimport { refresh } from \"@mittwald/react-use-promise\";\nimport { ErrorBoundary } from \"react-error-boundary\";\n\nconst App = () =\u003e (\n  \u003cMainLayout\u003e\n    \u003cMainMenu /\u003e\n    \u003cErrorBoundary\n      fallback={\u003cMainContentErrorFallback /\u003e}\n      onReset={() =\u003e {\n        refresh({ error: true });\n      }}\n    \u003e\n      \u003cSuspense fallback={\u003cMainContentLoadingSkeleton /\u003e}\u003e\n        \u003cMainContent /\u003e\n      \u003c/Suspense\u003e\n    \u003c/ErrorBoundary\u003e\n  \u003c/MainLayout\u003e\n);\n```\n\n## Best practices\n\n- Use async resources as deep in the component tree as possible (see\n  [Lazy loading with Async Resources](#-lazy-loading-with-async-resources)).\n- In larger applications use factory methods for your async resources. The\n  [`resourceify`](#resourceify) function helps you to quickly set up your\n  factory methods.\n- Add tags to your resources for a nuanced refreshing. You can even listen on\n  backend-events (maybe pushed via a socket-connection) to invalidate the\n  resources used in your session.\n- Use models for a coherent interaction with your business models. This is a\n  good practice in general, when your app has to deal with a large business\n  model.\n\n```tsx\n// File resources/user.ts\nimport { getUserProfile } from \"../resources/user\";\nimport { resourceify } from \"@mittwald/react-use-promise\";\n\nexport const getUserProfile = resourceify(apiClient.loadUserProfile);\n\n// File models/UserProfile.ts\nimport { getUserProfile } from \"../resources/user\";\n// ...\n\nexport class UserProfile {\n  public readonly id: string;\n  public readonly firstName: string;\n  public readonly lastName: string;\n\n  public constructor(data: UserProfileApiData) {\n    this.id = data.id;\n    this.firstName = data.firstName;\n    this.lastName = data.lastName;\n  }\n\n  public static useLoadById(id: string): UserProfile {\n    const data = getUserProfile([id]).use();\n    return new UserProfile(data);\n  }\n\n  public getFullName(): string {\n    return `${this.firstName} ${this.lastName}`;\n  }\n\n  public async updateName(firstName: string, lastName: string): Promise\u003cvoid\u003e {\n    await apiClient.updateProfile({\n      firstName,\n      lastName,\n    });\n  }\n}\n\n// File components/UserProfileName.tsx\nimport { UserProfile } from \"../models/UserProfile\";\n\nexport const UserProfileName: FC\u003c{ id: string }\u003e = (props) =\u003e {\n  const user = UserProfile.useLoadById(props.id);\n  return \u003c\u003e{user.getFullName()}\u003c/\u003e;\n};\n```\n\n## Migration guides\n\n### V1 to V2\n\n- For more naming consistency the `.watch()` method of the Async Resource has\n  changed to `.use()`. Replace all usages of `watch()` with `use()`.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmittwald%2Freact-use-promise","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmittwald%2Freact-use-promise","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmittwald%2Freact-use-promise/lists"}