{"id":16823403,"url":"https://github.com/digitalbrainjs/use-async-effect","last_synced_at":"2026-02-27T16:34:23.871Z","repository":{"id":43028324,"uuid":"326758893","full_name":"DigitalBrainJS/use-async-effect","owner":"DigitalBrainJS","description":"React async effect hook with cancellation, progress capturing, and other powers of CPromise","archived":false,"fork":false,"pushed_at":"2022-01-23T20:06:22.000Z","size":2824,"stargazers_count":20,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-18T07:50:44.002Z","etag":null,"topics":["async","fetch","hook","hooks","promise","react","react-hooks"],"latest_commit_sha":null,"homepage":"","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/DigitalBrainJS.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-01-04T17:30:07.000Z","updated_at":"2023-12-17T16:01:05.000Z","dependencies_parsed_at":"2022-07-07T22:28:50.019Z","dependency_job_id":null,"html_url":"https://github.com/DigitalBrainJS/use-async-effect","commit_stats":null,"previous_names":[],"tags_count":27,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DigitalBrainJS%2Fuse-async-effect","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DigitalBrainJS%2Fuse-async-effect/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DigitalBrainJS%2Fuse-async-effect/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DigitalBrainJS%2Fuse-async-effect/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/DigitalBrainJS","download_url":"https://codeload.github.com/DigitalBrainJS/use-async-effect/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244902929,"owners_count":20529114,"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":["async","fetch","hook","hooks","promise","react","react-hooks"],"created_at":"2024-10-13T11:07:55.215Z","updated_at":"2025-10-27T20:45:02.537Z","avatar_url":"https://github.com/DigitalBrainJS.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Build Status](https://travis-ci.com/DigitalBrainJS/use-async-effect.svg?branch=master)](https://travis-ci.com/DigitalBrainJS/use-async-effect)\n![npm](https://img.shields.io/npm/dm/use-async-effect2)\n![npm bundle size](https://img.shields.io/bundlephobia/minzip/use-async-effect)\n![David](https://img.shields.io/david/DigitalBrainJS/use-async-effect)\n[![Stars](https://badgen.net/github/stars/DigitalBrainJS/use-async-effect)](https://github.com/DigitalBrainJS/use-async-effect/stargazers)\n\n## useAsyncEffect2 :snowflake:\n\nThis library provides an async belt for the React components as: \n- `useAsyncEffect` - deeply cancellable asynchronous effects that can be cleared (canceled) on component unmounting, timeout, or by user request.\n- `useAsyncCallback` - cancellable async callbacks\n- `useAsyncDeepState` - to define a deep state, the actual values of which can be accessed from an async routine\n- `useAsyncWatcher` - to watch for state updates in a promise flow\n\nThe library is designed to make it as easy as possible to use complex and composite asynchronous routines \nin React components. It works on top of a [custom cancellable promise](https://www.npmjs.com/package/c-promise2),\nsimplifying the solution to many common challenges with asynchronous tasks. Can be composed with cancellable version of `Axios`\n([cp-axios](https://www.npmjs.com/package/cp-axios)) and `fetch API` ([cp-fetch](https://www.npmjs.com/package/cp-fetch))\nto get auto cancellable React async effects/callbacks with network requests.\n\n### Quick start\n\n1. You have to use the generator syntax instead of ECMA async functions, basically by replacing `await` with `yield`\n and  `async()=\u003e{}` or `async function()` with `function*`:\n \n    ````javascript\n    // plain React effect using `useEffect` hook\n    useEffect(()=\u003e{\n      const doSomething = async()=\u003e{\n        await somePromiseHandle;\n        setStateVar('foo');\n      };\n      doSomething();\n    }, [])\n    // auto-cleanable React async effect using `useAsyncEffect` hook\n    useAsyncEffect(function*(){\n      yield somePromiseHandle;\n      setStateVar('foo');\n    }, [])\n    ````\n\n1. It's recommended to use [`CPromise`](https://www.npmjs.com/package/c-promise2) instead of the native Promise to make\n the promise chain deeply cancellable, at least if you're going to change the component state inside it.\n\n    ````javascript\n    import { CPromise } from \"c-promise2\";\n    \n    const MyComponent= ()=\u003e{\n        const [text, setText]= useState('');\n        \n        useAsyncEffect(function*(){\n          yield CPromise.delay(1000);\n          setText('Hello!');\n        });\n    }\n    ````\n\n1. Don't catch (or just rethrow caught) `CanceledError` errors with `E_REASON_UNMOUNTED` \nreason inside your code before making any stage change:\n\n    ````javascript\n    import {\n      useAsyncEffect,\n      E_REASON_UNMOUNTED,\n      CanceledError\n    } from \"use-async-effect2\";\n    import cpAxios from \"cp-axios\";\n    \n    const MyComponent= ()=\u003e{\n        const [text, setText]= useState('');\n        \n        useAsyncEffect(function*(){\n          try{\n              const json= (yield cpAxios('http://localhost/')).data;\n              setText(`Data: ${JSON.stringify(json)}`);\n          }catch(err){\n              // just rethrow the CanceledError error if it has E_REASON_UNMOUNTED reason\n              CanceledError.rethrow(err, E_REASON_UNMOUNTED);\n              // otherwise work with it somehow\n              setText(`Failed: ${err.toString}`);\n          } \n        });\n    }\n    ````\n\n## Installation :hammer:\n- Install for node.js using npm/yarn:\n\n```bash\n$ npm install use-async-effect2\n```\n\n```bash\n$ yarn add use-async-effect2\n```\n\n## Why\nEvery asynchronous procedure in your component that changes its state must properly handle the unmount event\nand stop execution in some way before attempting to change the state of the unmounted component, otherwise\nyou will get the well-known React leakage warning:\n````\nWarning: Can't perform a React state update on an unmounted component. \nThis is an no-op, but it indicates a memory leak in your application. \nTo fix, cancel all subscriptions and asynchronous task in \"a useEffect cleanup function\".\n````\n\nIt uses [c-promise2](https://www.npmjs.com/package/c-promise2) to make it work. \nWhen used in conjunction with other libraries from CPromise ecosystem,\nsuch as [cp-fetch](https://www.npmjs.com/package/cp-fetch) and [cp-axios](https://www.npmjs.com/package/cp-axios),\nyou get a powerful tool for building asynchronous logic of React components.\n\n## Examples\n\n### useAsyncEffect\n\nA tiny `useAsyncEffect` demo with JSON fetching using internal states: \n\n[Live demo to play](https://codesandbox.io/s/use-async-effect-axios-minimal-pdngg?file=/src/TestComponent.js)\n\n````javascript\nfunction JSONViewer({ url, timeout }) {\n  const [cancel, done, result, err] = useAsyncEffect(function* () {\n      return (yield cpAxios(url).timeout(timeout)).data;\n    }, { states: true });\n\n  return (\n    \u003cdiv\u003e\n      {done ? (err ? err.toString() : JSON.stringify(result)) : \"loading...\"}\n      \u003cbutton className=\"btn btn-warning\" onClick={cancel} disabled={done}\u003e\n        Cancel async effect (abort request)\n      \u003c/button\u003e\n    \u003c/div\u003e\n  );\n}\n````\n\n[Another demo](https://codesandbox.io/s/use-async-effect-fetch-tiny-ui-xbmk2?file=/src/TestComponent.js)\n\n````jsx\nimport React from \"react\";\nimport {useState} from \"react\";\nimport {useAsyncEffect} from \"use-async-effect2\";\nimport cpFetch from \"cp-fetch\";\n\nfunction JSONViewer(props) {\n    const [text, setText] = useState(\"\");\n\n    useAsyncEffect(function* () {\n            setText(\"fetching...\"); \n            const response = yield cpFetch(props.url); // will throw a CanceledError if component get unmounted\n            const json = yield response.json();\n            setText(`Success: ${JSON.stringify(json)}`);\n    }, [props.url]);\n\n    return \u003cdiv\u003e{text}\u003c/div\u003e;\n}\n````\nNotice: the related network request will be aborted, when unmounting.\n\nAn example with a timeout \u0026 error handling ([Live demo](https://codesandbox.io/s/async-effect-demo1-vho29?file=/src/TestComponent.js)):\n````jsx\nimport React, { useState } from \"react\";\nimport { useAsyncEffect, E_REASON_UNMOUNTED, CanceledError} from \"use-async-effect2\";\nimport cpFetch from \"cp-fetch\";\n\nexport default function TestComponent(props) {\n  const [text, setText] = useState(\"\");\n  const [isPending, setIsPending] = useState(true);\n\n  const cancel = useAsyncEffect(\n    function* ({ onCancel }) {\n      console.log(\"mount\");\n\n      this.timeout(props.timeout);\n\n      onCancel(() =\u003e console.log(\"scope canceled\"));\n\n      try {\n        setText(\"fetching...\");\n        const response = yield cpFetch(props.url);\n        const json = yield response.json();\n        setIsPending(false);\n        setText(`Success: ${JSON.stringify(json)}`);\n      } catch (err) {\n        CanceledError.rethrow(err, E_REASON_UNMOUNTED); //passthrough for UNMOUNTED rejection\n        setIsPending(false);\n        setText(`Failed: ${err}`);\n      }\n\n      return () =\u003e {\n        console.log(\"unmount\");\n      };\n    },\n    [props.url]\n  );\n\n  return (\n    \u003cdiv className=\"component\"\u003e\n      \u003cdiv className=\"caption\"\u003euseAsyncEffect demo:\u003c/div\u003e\n      \u003cdiv\u003e{text}\u003c/div\u003e\n      \u003cbutton onClick={cancel} disabled={!isPending}\u003e\n        Cancel request\n      \u003c/button\u003e\n    \u003c/div\u003e\n  );\n}\n````\n\n### useAsyncCallback\n\nHere's a [Demo App](https://codesandbox.io/s/use-async-callback-demo-app-yyic4?file=/src/TestComponent.js) to play with\n`asyncCallback` and learn about its options.\n\nLive search for character from the `rickandmorty` universe using `rickandmortyapi.com`:\n\n[Live demo](https://codesandbox.io/s/use-async-effect-axios-rickmorty-search-ui-sd2mv?file=/src/TestComponent.js)\n````jsx\nimport React, { useState } from \"react\";\nimport {\n  useAsyncCallback,\n  E_REASON_UNMOUNTED,\n  CanceledError\n} from \"use-async-effect2\";\nimport { CPromise } from \"c-promise2\";\nimport cpAxios from \"cp-axios\";\n\nexport default function TestComponent(props) {\n  const [text, setText] = useState(\"\");\n\n  const handleSearch = useAsyncCallback(\n    function* (event) {\n      const { value } = event.target;\n      if (value.length \u003c 3) return;\n      yield CPromise.delay(1000);\n      setText(\"searching...\");\n      try {\n        const response = yield cpAxios(\n          `https://rickandmortyapi.com/api/character/?name=${value}`\n        ).timeout(props.timeout);\n        setText(response.data?.results?.map(({ name }) =\u003e name).join(\",\"));\n      } catch (err) {\n        CanceledError.rethrow(err, E_REASON_UNMOUNTED);\n        setText(err.response?.status === 404 ? \"Not found\" : err.toString());\n      }\n    },\n    { cancelPrevious: true }\n  );\n\n  return (\n    \u003cdiv className=\"component\"\u003e\n      \u003cdiv className=\"caption\"\u003e\n        useAsyncCallback demo: Rickandmorty universe character search\n      \u003c/div\u003e\n      Character name: \u003cinput onChange={handleSearch}\u003e\u003c/input\u003e\n      \u003cdiv\u003e{text}\u003c/div\u003e\n      \u003cbutton className=\"btn btn-warning\" onClick={handleSearch.cancel}\u003e\n        Cancel request\n      \u003c/button\u003e\n    \u003c/div\u003e\n  );\n}\n````\nThis code handles the cancellation of the previous search sequence (including aborting the request) and\ncanceling the sequence when the component is unmounted to avoid the React leak warning.\n\n`useAsyncCallback` example: fetch with progress capturing \u0026 cancellation \n ([Live demo](https://codesandbox.io/s/use-async-callback-axios-catch-ui-l30h5?file=/src/TestComponent.js)):\n````javascript\nimport React, { useState } from \"react\";\nimport { useAsyncCallback, E_REASON_UNMOUNTED } from \"use-async-effect2\";\nimport { CPromise, CanceledError } from \"c-promise2\";\nimport cpAxios from \"cp-axios\";\nimport { ProgressBar } from \"react-bootstrap\";\n\nexport default function TestComponent(props) {\n  const [text, setText] = useState(\"\");\n  const [progress, setProgress] = useState(0);\n  const [isFetching, setIsFetching] = useState(false);\n\n  const fetchUrl = useAsyncCallback(\n    function* (options) {\n      try {\n        setIsFetching(true);\n        this.innerWeight(3); // for progress calculation\n        this.progress(setProgress);\n        setText(\"fetching...\");\n        const response = yield cpAxios(options).timeout(props.timeout);\n        yield CPromise.delay(500); // just for fun\n        yield CPromise.delay(500); // just for fun\n        setText(JSON.stringify(response.data));\n        setIsFetching(false);\n      } catch (err) {\n        CanceledError.rethrow(err, E_REASON_UNMOUNTED);\n        setText(err.toString());\n        setIsFetching(false);\n      }\n    },\n    [props.url]\n  );\n\n  return (\n    \u003cdiv className=\"component\"\u003e\n      \u003cdiv className=\"caption\"\u003euseAsyncEffect demo:\u003c/div\u003e\n      \u003cdiv\u003e{isFetching ? \u003cProgressBar now={progress * 100} /\u003e : text}\u003c/div\u003e\n      {!isFetching ? (\n        \u003cbutton\n          className=\"btn btn-success\"\n          onClick={() =\u003e fetchUrl(props.url)}\n          disabled={isFetching}\n        \u003e\n          Fetch data\n        \u003c/button\u003e\n      ) : (\n        \u003cbutton\n          className=\"btn btn-warning\"\n          onClick={() =\u003e fetchUrl.cancel()}\n          disabled={!isFetching}\n        \u003e\n          Cancel request\n        \u003c/button\u003e\n      )}\n    \u003c/div\u003e\n  );\n}\n````\n\n### useAsyncDeepState\n\nAn enhancement of the useState hook for use inside async routines. \nIt defines a deep state abd works very similar to the React `setState` class method.\nThe hook returns a promise that will be fulfilled with an array of newState and oldState values\nafter the state has changed.\n\n````javascript\nexport default function TestComponent(props) {\n\n  const [state, setState] = useAsyncDeepState({\n    foo: 123,\n    bar: 456,\n    counter: 0\n  });\n\n  return (\n    \u003cdiv className=\"component\"\u003e\n      \u003cdiv className=\"caption\"\u003euseAsyncDeepState demo:\u003c/div\u003e\n      \u003cdiv\u003e{state.counter}\u003c/div\u003e\n      \u003cbutton onClick={async()=\u003e{\n        const newState= await setState((state)=\u003e {\n          return {counter: state.counter + 1}\n        });\n\n        console.log(`Updated: ${newState.counter}`);\n      }}\u003eInc\u003c/button\u003e\n      \u003cbutton onClick={()=\u003esetState({\n        counter: state.counter\n      })}\u003eSet the same state value\u003c/button\u003e\n    \u003c/div\u003e\n  );\n}\n````\n\n### useAsyncWatcher\n\nThis hook is a promisified abstraction on top of the `useEffect` hook. The hook returns the watcher function that resolves\nits promise when one of the watched dependencies have changed.\n\n````javascript\nexport default function TestComponent7(props) {\n  const [value, setValue] = useState(0);\n\n  const [fn, cancel, pending, done, result, err] = useAsyncCallback(function* () {\n    console.log('inside callback the value is:', value);\n    return (yield cpAxios(`https://rickandmortyapi.com/api/character/${value}`)).data;\n  }, {states: true, deps: [value]})\n\n  const callbackWatcher = useAsyncWatcher(fn);\n\n  return (\n    \u003cdiv className=\"component\"\u003e\n      \u003cdiv className=\"caption\"\u003euseAsyncWatcher demo:\u003c/div\u003e\n      \u003cdiv\u003e{pending ? \"loading...\" : (done ? err ? err.toString() : JSON.stringify(result, null, 2) : \"\")}\u003c/div\u003e\n      \u003cinput value={value} type=\"number\" onChange={async ({target}) =\u003e {\n        setValue(target.value * 1);\n        const [fn]= await callbackWatcher();\n        await fn();\n      }}/\u003e\n      {\u003cbutton onClick={cancel} disabled={!pending}\u003eCancel async effect\u003c/button\u003e}\n    \u003c/div\u003e\n  );\n}\n````\n\nTo learn more about available features, see the c-promise2 [documentation](https://www.npmjs.com/package/c-promise2).\n\n### Wiki\n\nSee the [Project Wiki](https://github.com/DigitalBrainJS/use-async-effect/wiki) to get the most exhaustive guide.\n\n## Playground\n\nTo get it, clone the repository and run `npm run playground` in the project directory or\njust use the codesandbox [demo](https://codesandbox.io/s/async-effect-demo1-vho29) to play with the library online.\n\n## API\n\n### useAsyncEffect(generatorFn, deps?: []): (cancel():boolean)\n### useAsyncEffect(generatorFn, options?: object): (cancel():boolean)\nA React hook based on [`useEffect`](https://reactjs.org/docs/hooks-effect.html), that resolves passed generator as asynchronous function. \nThe asynchronous generator sequence and its promise of the result will be canceled if \nthe effect cleanup process started before it completes.\nThe generator can return a cleanup function similar to the `useEffect` hook. \n- `generatorFn(scope: CPromise)` : `GeneratorFunction` - generator to resolve as an async function. \nGenerator context (`this`) refers to the CPromise instance.\n- `deps?: any[] | UseAsyncEffectOptions` - effect dependencies\n\n#### UseAsyncEffectOptions:\n- `options.deps?: any[]` - effect dependencies\n- `options.skipFirst?: boolean` - skip first render\n- `options.states: boolean= false` - use states\n- `options.once: boolean= false` - run the effect only once (the effect's async routine should be fully completed)\n\n#### Available states vars:\n- `done: boolean` - the function execution is completed (with success or failure)\n- `result: any` - refers to the resolved function result\n- `error: object` - refers to the error object. This var is always set when an error occurs.\n- `canceled:boolean` - is set to true if the function has been failed with a `CanceledError`.\n\nAll these vars defined on the returned `cancelFn` function and can be alternative reached through\nthe iterator interface in the following order: \n````javascript\nconst [cancelFn, done, result, error, canceled]= useAsyncEffect(/*code*/);\n````\n\n### useAsyncCallback(generatorFn, deps?: []): CPromiseAsyncFunction\n### useAsyncCallback(generatorFn, options?: object): CPromiseAsyncFunction\nThis hook makes an async callback that can be automatically canceled on unmount or by user request.\n- `generatorFn([scope: CPromise], ...userArguments)` : `GeneratorFunction` - generator to resolve as an async function. \nGenerator context (`this`) and the first argument (if `options.scopeArg` is set) refer to the CPromise instance.\n- `deps?: any[] | UseAsyncCallbackOptions` - effect dependencies\n#### UseAsyncCallbackOptions:\n- `deps: any[]` - effect dependencies \n- `combine:boolean` - subscribe to the result of the async function already running with the same arguments instead\n of running a new one. \n- `cancelPrevious:boolean` - cancel the previous pending async function before running a new one. \n- `threads: number=0` - set concurrency limit for simultaneous calls. `0` means unlimited.\n- `queueSize: number=0` - set max queue size.\n- `scopeArg: boolean=false` - pass `CPromise` scope to the generator function as the first argument.\n- `states: boolean=false` - enable state changing. The function must be single threaded to use the states.\n\n#### Available state vars:\n- `pending: boolean` - the function is in the pending state\n- `done: boolean` - the function execution completed (with success or failure)\n- `result: any` - refers to the resolved function result\n- `error: object` - refers to the error object. This var always set when an error occurs.\n- `canceled:boolean` - is set to true if the function has been failed with a `CanceledError`.\n\nAll these vars defined on the decorated function and can be alternative reached through\nthe iterator interface in the following order: \n````javascript\nconst [decoratedFn, cancel, pending, done, result, error, canceled]= useAsyncCallback(/*code*/);\n````\n### useAsyncDeepState([initialValue?: object]): ([value: any, accessor: function])\n#### arguments\n- `initialValue`\n#### returns\nIterable of:\n- `value: object` - current state value\n- `accessor:(newValue)=\u003ePromise\u003crawStateValue:any\u003e` - promisified setter function that can be used\n as a getter if called without arguments\n\n### useAsyncWatcher([...valuesToWatch]): watcherFn\n#### arguments\n- `...valuesToWatch: any` - any values to watch that will be passed to the internal effect hook\n#### returns\n- `watcherFn: ([grabPrevValue= false]): Promise\u003c[newValue, [prevValue]]\u003e` - if the hook is watching one value\n- `watcherFn: ([grabPrevValue= false]): Promise\u003c[...[newValue, [prevValue]]]\u003e` - if the hook is watching multiple values\n\n## Related projects\n- [c-promise2](https://www.npmjs.com/package/c-promise2) - promise with cancellation, decorators, timeouts, progress capturing, pause and user signals support\n- [cp-axios](https://www.npmjs.com/package/cp-axios) - a simple axios wrapper that provides an advanced cancellation api\n- [cp-fetch](https://www.npmjs.com/package/cp-fetch) - fetch with timeouts and request cancellation\n- [cp-koa](https://www.npmjs.com/package/cp-koa) - koa with middlewares cancellation\n\n## License\n\nThe MIT License Copyright (c) 2021 Dmitriy Mozgovoy robotshara@gmail.com\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\nINCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR\nPURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\nDAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdigitalbrainjs%2Fuse-async-effect","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdigitalbrainjs%2Fuse-async-effect","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdigitalbrainjs%2Fuse-async-effect/lists"}