{"id":18064741,"url":"https://github.com/franciscop/use-async","last_synced_at":"2025-04-11T18:11:25.737Z","repository":{"id":45683932,"uuid":"431566925","full_name":"franciscop/use-async","owner":"franciscop","description":"React hooks to make handling async operations a breeze","archived":false,"fork":false,"pushed_at":"2024-11-14T16:04:21.000Z","size":23,"stargazers_count":10,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-25T14:06:35.178Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","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/franciscop.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","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},"funding":{"custom":"https://www.paypal.me/franciscopresencia/19"}},"created_at":"2021-11-24T17:07:30.000Z","updated_at":"2024-11-14T16:04:26.000Z","dependencies_parsed_at":"2024-11-14T17:19:03.236Z","dependency_job_id":"e59b8657-2e42-4502-b16c-74c7a27a53eb","html_url":"https://github.com/franciscop/use-async","commit_stats":{"total_commits":14,"total_committers":1,"mean_commits":14.0,"dds":0.0,"last_synced_commit":"c348bd84af2f64fc42581e9516428aeeeed26683"},"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/franciscop%2Fuse-async","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/franciscop%2Fuse-async/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/franciscop%2Fuse-async/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/franciscop%2Fuse-async/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/franciscop","download_url":"https://codeload.github.com/franciscop/use-async/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247965860,"owners_count":21025451,"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":[],"created_at":"2024-10-31T06:07:26.704Z","updated_at":"2025-04-11T18:11:25.719Z","avatar_url":"https://github.com/franciscop.png","language":"JavaScript","funding_links":["https://www.paypal.me/franciscopresencia/19"],"categories":[],"sub_categories":[],"readme":"# Use Async [![npm install use-async](https://img.shields.io/badge/npm%20install-use--async-blue.svg \"install badge\")](https://www.npmjs.com/package/use-async) [![test badge](https://github.com/franciscop/use-async/workflows/tests/badge.svg \"test badge\")](https://github.com/franciscop/use-async/blob/master/.github/workflows/tests.yml) [![gzip size](https://img.badgesize.io/franciscop/use-async/master/src/index.js.svg?compression=gzip \"gzip badge\")](https://github.com/franciscop/use-async/blob/master/src/index.js)\n\nLike useEffect, but async for ease of use:\n\n```js\nimport { useAsyncEffect } from \"use-async\";\n\n// A React hook, so follow usual React Hook rules:\nuseAsyncEffect(async () =\u003e {\n  const info = await someAsyncOp();\n  setState(info);\n}, [id]);\n```\n\nThe effect receives a `signal` that can be used with `fetch()`, axios, etc. to cancel ongoing promises:\n\n```js\nimport { useAsyncEffect } from \"use-async\";\n\nuseAsyncEffect(async (signal) =\u003e {\n  const res = await axios.get(\"/users\", { signal });\n  setState(res.data);\n}, []);\n```\n\nThis library has two named exports (feel free [to propose more](https://github.com/franciscop/use-async/discussions)!):\n\n- [`useAsyncEffect`](#useAsyncEffect): handle async effects without race conditions\n- [`useAsyncData`](#useAsyncData): handle data fetching operations and dependencies\n\n## Getting Started\n\nFirst install the library in your React (16.8+) project:\n\n```\nnpm install use-async\n```\n\nThen import either of the async functions:\n\n```js\nimport { useAsyncEffect } from \"use-async\";\n```\n\nFinally, use the hook within your component to do data fetching or other async operations:\n\n```js\nimport { useAsyncEffect } from \"use-async\";\n\nexport default function UserProfile({ id }) {\n  const [profile, setProfile] = useState(null);\n\n  useAsyncEffect(\n    async (signal) =\u003e {\n      const res = await axios.get(\"/users/\" + id, { signal });\n      setProfile(res.data);\n    },\n    [id]\n  );\n\n  if (!profile) return \u003cSpinner /\u003e;\n\n  return (\n    \u003cdiv\u003e\n      \u003ch1\u003e{profile.name}\u003c/h1\u003e...\n    \u003c/div\u003e\n  );\n}\n```\n\n## API\n\nThis library has two named exports:\n\n- [`useAsyncEffect`](#useAsyncEffect): handle async effects without race conditions\n- [`useAsyncData`](#useAsyncData): handle data fetching operations and dependencies\n\nSome shared points on both functions:\n\n- The signature of both is first an _async_ function, and second the dependencies array.\n- The _async_ function receives as arguments first the signal, and then the spread of the dependencies.\n- The signal will be _aborted_ either when the component itself unmounts, or when the dependencies for the hook change. AbortErrors are automatically catched so you don't need to worry about _those_.\n- `useAsyncData` is a wrapper of `useAsyncEffect` for convenience, to make it easier for fetching data asynchronously to use in the current component.\n- This library solves two major problems with the traditional `useEffect()`: async functions and race conditions. See [this article by Max Rozen](https://maxrozen.com/race-conditions-fetching-data-react-with-useeffect) about one of the main problems this library solves.\n\n\u003e The return of the hooks is different, as well as the expected return from the _async_ callbacks. Please read the documentation below for details.\n\n### `useAsyncEffect()`\n\nThis is a similar hook to `useEffect()`, but explicitly designed to work asynchronously and to make it easy to handle race conditions:\n\n```js\nimport { useAsyncEffect } from \"use-async\";\n\n// Easily handle API calls\nconst [profile, setProfile] = useState(null);\nuseAsyncEffect(\n  async (signal) =\u003e {\n    const data = await getUserProfile(id);\n    if (signal.aborted) return; // \u003c= Avoid race conditions on the network!\n    setProfile(data);\n  },\n  [id]\n);\n```\n\n\u003e Note: the above can be simplified even further with useAsyncData() below, but we think it's a very common usage so wanted to give a familiar example to the reader.\n\nThe arguments passed to the _async_ function inside useAsyncEffect() are:\n\n1. `signal`: an [AbortSignal](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) that will be aborted if the component is unmounted or the function becomes stale (when the dependencies change). If the dependencies are an empty array, then it will only indicate when the component is unmounted.\n2. `dep1`: the first dependency from the array of dependencies.\n3. `dep2`: the second dependency from the array of dependencies.\n4. etc.\n\nThe `signal` is a standard [AbortSignal instance](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal), which both `fetch()` and `axios()` accept out of the box. This means you can cancel ongoing requests that have become stale/unwanted:\n\n```js\n// Aborts the request if it becomes invalid while ongoing\nconst [profile, setProfile] = useState(null);\nuseAsyncEffect(\n  async (signal) =\u003e {\n    const res = await axios.get(`/users/${id}`, { signal });\n    setProfile(res.data);\n  },\n  [id]\n);\n\n// Aborts the request if it becomes invalid while ongoing\nconst [profile, setProfile] = useState(null);\nuseAsyncEffect(\n  async (signal) =\u003e {\n    const res = await fetch(`/api/users/${id}`, { signal });\n    const data = await res.json();\n    setProfile(data);\n  },\n  [id]\n);\n```\n\n\u003e It is normally to cancel any ongoing request if you know it's stale. It's normally not done for how hard it used to be compared to the light benefit of avoiding extra requests, but as you can see above with use-async it becomes easier than ever to abort stale requests!\n\nYou can add a cleanup function in two different ways: if the return value is a function, or adding an event listener to `signal`. The former is the easiest and most straightforward when you have a single async operation, but the latter might simplify your code if you have a complex series of async operations:\n\n```js\n// Simple example: adding a single side effect\nuseAsyncEffect(async signal =\u003e {\n  const res1 = await op1();\n  if (signal.aborted) return;\n  const id = setTimeout(() =\u003e {...}, 1000);\n  return () =\u003e {\n    clearTimeout(id);\n  };\n}, [id]);\n\n// Complex example: adding multiple side effects and cleanups\nuseAsyncEffect(async signal =\u003e {\n  const res1 = await op1();\n  if (signal.aborted) return;\n  const id1 = setTimeout(() =\u003e {\n    ...\n  }, 1000);\n  signal.addEventListener(\"abort\", () =\u003e clearTimeout(id1));\n\n  const res2 = await op2();\n  if (signal.aborted) return;\n  const id2 = setTimeout(() =\u003e {\n    ...\n  }, 2000);\n  signal.addEventListener(\"abort\", () =\u003e clearTimeout(id2));\n}, [id]);\n```\n\n### `useAsyncData()`\n\nThis is a helper for those cases when you are fetching data in the async function and setting it to a local variable in the current component. It includes a state machine to make it even easier:\n\n```js\nimport { useAsyncData } from 'use-async';\n\nconst myAsyncOperation = async (signal, id) =\u003e {...};\n\nexport default function MyAsyncComponent({ id }) {\n  const [data, status] = useAsyncData(myAsyncOperation, [id]);\n\n  if (status === \"LOADING\") return \u003cSpinner /\u003e;\n  if (status === \"ERROR\") return \u003cdiv\u003e{data.message}\u003c/div\u003e;\n\n  // Whatever the data is and you want to display\n  return \u003cdiv\u003e{data.name}\u003c/div\u003e;\n}\n```\n\nIt simplifies the fetching of data and the loading around it. The state machine can be completely ignored if you want a quick and easy usage, you just need to check that the data has the proper structure:\n\n```js\nexport default function MyAsyncComponent({ id }) {\n  const [data] = useAsyncData(myAsyncOperation, [id]);\n\n  // Whatever the data is and you want to display\n  return (\n    \u003cul\u003e{Array.isArray(data) ? data.map((item) =\u003e \u003cli\u003e{item}\u003c/li\u003e) : null}\u003c/ul\u003e\n  );\n}\n```\n\nThe arguments passed to the _async_ function inside useAsyncData() are:\n\n1. `signal`: an [AbortSignal](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) that will be aborted if the component is unmounted or the function becomes stale (when the dependencies change). If the dependencies are an empty array, then it will only indicate when the component is unmounted.\n2. `dep1`: the first dependency from the array of dependencies.\n3. `dep2`: the second dependency from the array of dependencies.\n4. etc.\n\nSo, the dependencies will be passed as arguments to this callback. This makes it a easier to extract the callback as a different function if wanted, specially since the data will be set when returned from the function:\n\n```js\nimport { useAsyncData } from \"use-async\";\n\n// Extract it into a single function accepting the signal and the deps as args.\nconst getUserProfile = async (signal, id) =\u003e {\n  const res = await fetch(`/users/${id}`, { signal });\n  const data = await res.json();\n  return data;\n};\n\nconst MyComponent = ({ id }) =\u003e {\n  // Provide the callback and deps; which are injected as args after \"signal\"\n  const [profile] = useAsyncData(getUserProfile, [id]);\n\n  // ...\n};\n```\n\nThe default value should be done by using the destructuring default value:\n\n```js\nconst [data = \"myDefaultValue\", status] = useAsyncData(...);\n```\n\nThe `\"LOADING\"` status might be shown even when `data` is defined. This happens when the previous data is stale, and it gives you enough flexibility to decide what to do while loading the new data. You can hide the stale data, dim it out, overlay a loading indicator over it, etc.\n\nSome examples on how to deal with the stale data while loading new data:\n\n```js\nconst [data, status] = useAsyncData(...);\n\n// Example 1 - replace the whole page for a spinner while loading new data\nif (status === \"LOADING\") return \u003cSpinner /\u003e;\nreturn \u003cItemList data={data} /\u003e;\n\n// Example 2 - replace only a part of the page for a spinner\nreturn (\n  \u003cdiv\u003e\n    {status === \"LOADING\" ? \u003cSpinner /\u003e : \u003cItemList data={data} /\u003e}\n  \u003c/div\u003e\n);\n\n// Example 3 - overlay a spinner on top of the stale data\nreturn (\n  \u003cPage overlaySpinner={status === \"LOADING\"}\u003e\n    \u003cItemList data={data} /\u003e\n  \u003c/Page\u003e\n);\n\n// Example 4 - show a small spinner on top, similar to pulling down on Twitter\nreturn (\n  \u003cdiv\u003e\n    {status === \"LOADING\" \u0026\u0026 \u003cSmallSpinner /\u003e}\n    \u003cItemList data={data} /\u003e\n  \u003c/div\u003e\n);\n\n// etc\n```\n\n\u003e Note: assuming that if there's no \"data\", ItemList graciously shows a message\n\n## Examples\n\n### Simple profile fetch\n\nAs we saw before, this is a simple profile fetch that also avoids race conditions:\n\n```js\n// Easily handle API calls\nconst [profile, setProfile] = useState(null);\nuseAsyncEffect(\n  async (signal) =\u003e {\n    const res = await axios.get(`/users/${id}`);\n    if (signal.aborted) return; // \u003c= Avoid race conditions on the network!\n    setProfile(res.data);\n  },\n  [id]\n);\n```\n\nSince Axios (and `fetch()`) accept the `signal` as an option, the above can also be converted to:\n\n```js\nconst [profile, setProfile] = useState(null);\nuseAsyncEffect(\n  async (signal) =\u003e {\n    const res = await axios.get(`/users/${id}`, { signal });\n    setProfile(res.data);\n  },\n  [id]\n);\n```\n\nWe also export `useAsyncData`, which makes the above even easier:\n\n```js\nconst [profile, status] = useAsyncData(\n  async (signal) =\u003e {\n    const res = await axios.get(`/users/${id}`);\n    return res.data;\n  },\n  [id]\n);\n```\n\nFinally, the simplest we can do is if we either make axios return simply the data instead of the response (with an interceptor) or we put that as a separated function:\n\n```js\n// Outside our component\nconst getProfile = async (signal, id) =\u003e {\n  const res = await axios.get(`/users/${id}`);\n  return res.data;\n};\n\nexport default function UserProfile({ id }) {\n  const [profile, status] = useAsyncData(getProfile, [id]);\n\n  return (...);\n};\n```\n\nIf we want to do the same with the native `useEffect`, it becomes a lot more cumbersome since now we need to track the status manually:\n\n```js\nconst [profile, setProfile] = useState(null);\nuseEffect(() =\u003e {\n  let isActive = true;\n  axios.get(`/users/${id}`).then((res) =\u003e {\n    if (!isActive) return;\n    setProfile(res.data);\n  });\n  return () =\u003e {\n    isActive = false;\n  };\n}, [id]);\n```\n\nFor this code, that has the issue that it doesn't even check if the current page is still mounted before killing it:\n\n```js\n// How you might be doing it now\nconst [state, setState] = useState(null);\nuseEffect(() =\u003e {\n  axios.get(\"/pages/\" + id).then((res) =\u003e {\n    setState(res.data);\n  });\n}, [id]);\n```\n\nEasily handle async API calls:\n\n```js\n// New way of doing it\nconst [state, setState] = useState(null);\nuseAsyncEffect(\n  async (signal) =\u003e {\n    const res = await axios.get(\"/pages/\" + id);\n    if (signal.aborted) return;\n    setState(res.data);\n  },\n  [id]\n);\n```\n\n### Compare to `@n1ru4l/use-async-effect`\n\nThis library for use-async-effect gets some bits right (we should support generators at some point!), but IMHO it still gives you too many shotguns to shot your foot with. Let's compare their clean example given here with our code:\n\n```js\n// After 🤩\nimport useAsyncEffect from \"@n1ru4l/use-async-effect\";\n\nconst MyComponent = ({ filter }) =\u003e {\n  const [data, setData] = useState(null);\n\n  useAsyncEffect(\n    function* (onCancel, c) {\n      const controller = new AbortController();\n\n      onCancel(() =\u003e controller.abort());\n\n      const data = yield* c(\n        fetch(\"/data?filter=\" + filter, {\n          signal: controller.signal,\n        }).then((res) =\u003e res.json())\n      );\n\n      setData(data);\n    },\n    [filter]\n  );\n\n  return data ? \u003cRenderData data={data} /\u003e : null;\n};\n```\n\nOur solution of the same problem is this:\n\n```js\n// ✅ Name easier to remember\nimport { useAsyncEffect } from \"use-async\";\n\nconst MyComponent = ({ filter }) =\u003e {\n  const [data, setData] = useState(null);\n\n  // ✅ Signal is already provided by the library\n  useAsyncEffect(\n    async (signal) =\u003e {\n      // ✅ More readable code, so easier to follow workflow\n      // ✅ await is simpler than a generator+yield\n      // ✅ signal will cancel if the component is unmounted or the deps change\n      const res = await fetch(\"/data?filter=\" + filter, { signal });\n      const data = await res.json();\n      setData(data);\n    },\n    [filter]\n  );\n\n  return data ? \u003cRenderData data={data} /\u003e : null;\n};\n```\n\nThe implementation with our library (`use-async`) is half of the lines of code (10 vs 18) while keeping your code legible and straightforward.\n\nWe've looked at this and other existing libraries, and found that we could improve meaningful upon them. That's why we decided to launch `use-async` on 2021 instead of using one of the existing ones.\n\n## Thanks\n\nSpecial thanks to:\n\n- Max Rozen's [great article](https://maxrozen.com/race-conditions-fetching-data-react-with-useeffect) on using AbortSignal with useEffect. I had a rough idea on how to proceed, and that article cemented it!\n- `use-async-effect` (to which I contributed the `isMounted()` check) for being what I've been using for a while. It's [what I've learned](https://github.com/rauldeheer/use-async-effect/issues/13) by using it that allowed me to create `use-async`.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffranciscop%2Fuse-async","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffranciscop%2Fuse-async","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffranciscop%2Fuse-async/lists"}