react-async-hook
Advanced tools
Comparing version 2.3.0 to 3.0.0
@@ -9,2 +9,3 @@ export declare type AsyncState<R> = { | ||
declare type SetError<R> = (error: Error, asyncState: AsyncState<R>) => AsyncState<R>; | ||
declare type MaybePromise<T> = Promise<T> | T; | ||
export declare type UseAsyncOptionsNormalized<R> = { | ||
@@ -19,10 +20,11 @@ initialState: () => AsyncState<R>; | ||
export declare type UseAsyncOptions<R> = Partial<UseAsyncOptionsNormalized<R>> | undefined | null; | ||
export declare type UseAsyncReturn<R> = AsyncState<R> & { | ||
export declare type UseAsyncReturn<R, Args extends any[]> = AsyncState<R> & { | ||
set: (value: AsyncState<R>) => void; | ||
execute: () => Promise<R>; | ||
execute: (...args: Args) => Promise<R>; | ||
currentPromise: Promise<R> | null; | ||
}; | ||
export declare const useAsync: <R, Args extends any[]>(asyncFunction: (...args: Args) => Promise<R>, params: Args, options?: UseAsyncOptions<R>) => UseAsyncReturn<R>; | ||
export declare const useAsync: <R, Args extends any[]>(asyncFunction: (...args: Args) => Promise<R>, params: Args, options?: UseAsyncOptions<R>) => UseAsyncReturn<R, Args>; | ||
declare type AddArg<H, T extends any[]> = ((h: H, ...t: T) => void) extends ((...r: infer R) => void) ? R : never; | ||
export declare const useAsyncAbortable: <R, Args extends any[]>(asyncFunction: (...args: AddArg<AbortSignal, Args>) => Promise<R>, params: Args, options?: UseAsyncOptions<R>) => UseAsyncReturn<R>; | ||
export declare const useAsyncAbortable: <R, Args extends any[]>(asyncFunction: (...args: AddArg<AbortSignal, Args>) => Promise<R>, params: Args, options?: UseAsyncOptions<R>) => UseAsyncReturn<R, Args>; | ||
export declare const useAsyncCallback: <R, Args extends any[]>(asyncFunction: (...args: Args) => MaybePromise<R>) => UseAsyncReturn<R, Args>; | ||
export {}; |
@@ -61,3 +61,5 @@ 'use strict'; | ||
}; | ||
const useAsync = (asyncFunction, params, options) => { | ||
// Relaxed interface which accept both async and sync functions | ||
// Accepting sync function is convenient for useAsyncCallback | ||
const useAsyncInternal = (asyncFunction, params, options) => { | ||
const normalizedOptions = normalizeOptions(options); | ||
@@ -70,16 +72,24 @@ const AsyncState = useAsyncState(normalizedOptions); | ||
const shouldHandlePromise = (p) => isMounted() && CurrentPromise.is(p); | ||
const executeAsyncOperation = () => { | ||
const promise = asyncFunction(...params); | ||
CurrentPromise.set(promise); | ||
AsyncState.setLoading(); | ||
promise.then(result => { | ||
if (shouldHandlePromise(promise)) { | ||
AsyncState.setResult(result); | ||
} | ||
}, error => { | ||
if (shouldHandlePromise(promise)) { | ||
AsyncState.setError(error); | ||
} | ||
}); | ||
return promise; | ||
const executeAsyncOperation = (...args) => { | ||
const promise = asyncFunction(...args); | ||
if (promise instanceof Promise) { | ||
CurrentPromise.set(promise); | ||
AsyncState.setLoading(); | ||
promise.then(result => { | ||
if (shouldHandlePromise(promise)) { | ||
AsyncState.setResult(result); | ||
} | ||
}, error => { | ||
if (shouldHandlePromise(promise)) { | ||
AsyncState.setError(error); | ||
} | ||
}); | ||
return promise; | ||
} | ||
else { | ||
// We allow passing a non-async function (mostly for useAsyncCallback conveniency) | ||
const syncResult = promise; | ||
AsyncState.setResult(syncResult); | ||
return Promise.resolve(syncResult); | ||
} | ||
}; | ||
@@ -91,6 +101,6 @@ // Keep this outside useEffect, because inside isMounted() | ||
if (isMounting) { | ||
normalizedOptions.executeOnMount && executeAsyncOperation(); | ||
normalizedOptions.executeOnMount && executeAsyncOperation(...params); | ||
} | ||
else { | ||
normalizedOptions.executeOnUpdate && executeAsyncOperation(); | ||
normalizedOptions.executeOnUpdate && executeAsyncOperation(...params); | ||
} | ||
@@ -105,2 +115,3 @@ }, params); | ||
}; | ||
const useAsync = (asyncFunction, params, options) => useAsync(asyncFunction, params, options); | ||
const useAsyncAbortable = (asyncFunction, params, options) => { | ||
@@ -131,5 +142,20 @@ const abortControllerRef = react.useRef(); | ||
}; | ||
const useAsyncCallback = (asyncFunction) => { | ||
return useAsyncInternal(asyncFunction, | ||
// Hacky but in such case we don't need the params, | ||
// because async function is only executed manually | ||
[], { | ||
executeOnMount: false, | ||
executeOnUpdate: false, | ||
initialState: () => ({ | ||
loading: false, | ||
result: undefined, | ||
error: undefined, | ||
}), | ||
}); | ||
}; | ||
exports.useAsync = useAsync; | ||
exports.useAsyncAbortable = useAsyncAbortable; | ||
exports.useAsyncCallback = useAsyncCallback; | ||
//# sourceMappingURL=react-async-hook.cjs.development.js.map |
@@ -1,2 +0,2 @@ | ||
"use strict";var e=require("react");const t={loading:!0,result:void 0,error:void 0},r={initialState:()=>t,executeOnMount:!0,executeOnUpdate:!0,setLoading:e=>t,setResult:(e,t)=>({loading:!1,result:e,error:void 0}),setError:(e,t)=>({loading:!1,result:void 0,error:e})},n=(t,n,s)=>{const u=(e=>({...r,...e}))(s),o=(t=>{const[r,n]=e.useState(t.initialState);return{value:r,set:n,setLoading:()=>n(t.setLoading(r)),setResult:e=>n(t.setResult(e,r)),setError:e=>n(t.setError(e,r))}})(u),c=(()=>{const t=e.useRef(!1);return e.useEffect(()=>(t.current=!0,()=>{t.current=!1}),[]),()=>t.current})(),i=(()=>{const t=e.useRef(null);return{set:e=>t.current=e,get:()=>t.current,is:e=>t.current===e}})(),a=e=>c()&&i.is(e),l=()=>{const e=t(...n);return i.set(e),o.setLoading(),e.then(t=>{a(e)&&o.setResult(t)},t=>{a(e)&&o.setError(t)}),e},d=!c();return e.useEffect(()=>{d?u.executeOnMount&&l():u.executeOnUpdate&&l()},n),{...o.value,set:o.set,execute:l,currentPromise:i.get()}};exports.useAsync=n,exports.useAsyncAbortable=((t,r,s)=>{const u=e.useRef();return n(async(...e)=>{u.current&&u.current.abort();const r=new AbortController;u.current=r;try{return await t(r.signal,...e)}finally{u.current===r&&(u.current=void 0)}},r,s)}); | ||
"use strict";var e=require("react");const t={loading:!0,result:void 0,error:void 0},r={initialState:()=>t,executeOnMount:!0,executeOnUpdate:!0,setLoading:e=>t,setResult:(e,t)=>({loading:!1,result:e,error:void 0}),setError:(e,t)=>({loading:!1,result:void 0,error:e})},s=(t,s,n)=>{const u=(e=>({...r,...e}))(n),o=(t=>{const[r,s]=e.useState(t.initialState);return{value:r,set:s,setLoading:()=>s(t.setLoading(r)),setResult:e=>s(t.setResult(e,r)),setError:e=>s(t.setError(e,r))}})(u),c=(()=>{const t=e.useRef(!1);return e.useEffect(()=>(t.current=!0,()=>{t.current=!1}),[]),()=>t.current})(),i=(()=>{const t=e.useRef(null);return{set:e=>t.current=e,get:()=>t.current,is:e=>t.current===e}})(),a=e=>c()&&i.is(e),l=(...e)=>{const r=t(...e);if(r instanceof Promise)return i.set(r),o.setLoading(),r.then(e=>{a(r)&&o.setResult(e)},e=>{a(r)&&o.setError(e)}),r;{const e=r;return o.setResult(e),Promise.resolve(e)}},d=!c();return e.useEffect(()=>{d?u.executeOnMount&&l(...s):u.executeOnUpdate&&l(...s)},s),{...o.value,set:o.set,execute:l,currentPromise:i.get()}},n=(e,t,r)=>n(e,t,r);exports.useAsync=n,exports.useAsyncAbortable=((t,r,s)=>{const u=e.useRef();return n(async(...e)=>{u.current&&u.current.abort();const r=new AbortController;u.current=r;try{return await t(r.signal,...e)}finally{u.current===r&&(u.current=void 0)}},r,s)}),exports.useAsyncCallback=(e=>s(e,[],{executeOnMount:!1,executeOnUpdate:!1,initialState:()=>({loading:!1,result:void 0,error:void 0})})); | ||
//# sourceMappingURL=react-async-hook.cjs.production.js.map |
@@ -1,2 +0,2 @@ | ||
import{useEffect as t,useRef as e,useState as r}from"react";const n={loading:!0,result:void 0,error:void 0},o={initialState:()=>n,executeOnMount:!0,executeOnUpdate:!0,setLoading:t=>n,setResult:(t,e)=>({loading:!1,result:t,error:void 0}),setError:(t,e)=>({loading:!1,result:void 0,error:t})},s=(n,s,u)=>{const c=(t=>({...o,...t}))(u),i=(t=>{const[e,n]=r(t.initialState);return{value:e,set:n,setLoading:()=>n(t.setLoading(e)),setResult:r=>n(t.setResult(r,e)),setError:r=>n(t.setError(r,e))}})(c),a=(()=>{const r=e(!1);return t(()=>(r.current=!0,()=>{r.current=!1}),[]),()=>r.current})(),l=(()=>{const t=e(null);return{set:e=>t.current=e,get:()=>t.current,is:e=>t.current===e}})(),d=t=>a()&&l.is(t),g=()=>{const t=n(...s);return l.set(t),i.setLoading(),t.then(e=>{d(t)&&i.setResult(e)},e=>{d(t)&&i.setError(e)}),t},v=!a();return t(()=>{v?c.executeOnMount&&g():c.executeOnUpdate&&g()},s),{...i.value,set:i.set,execute:g,currentPromise:l.get()}},u=(t,r,n)=>{const o=e();return s(async(...e)=>{o.current&&o.current.abort();const r=new AbortController;o.current=r;try{return await t(r.signal,...e)}finally{o.current===r&&(o.current=void 0)}},r,n)};export{s as useAsync,u as useAsyncAbortable}; | ||
import{useRef as e,useEffect as t,useState as r}from"react";const n={loading:!0,result:void 0,error:void 0},o={initialState:()=>n,executeOnMount:!0,executeOnUpdate:!0,setLoading:e=>n,setResult:(e,t)=>({loading:!1,result:e,error:void 0}),setError:(e,t)=>({loading:!1,result:void 0,error:e})},s=(n,s,u)=>{const i=(e=>({...o,...e}))(u),c=(e=>{const[t,n]=r(e.initialState);return{value:t,set:n,setLoading:()=>n(e.setLoading(t)),setResult:r=>n(e.setResult(r,t)),setError:r=>n(e.setError(r,t))}})(i),a=(()=>{const r=e(!1);return t(()=>(r.current=!0,()=>{r.current=!1}),[]),()=>r.current})(),l=(()=>{const t=e(null);return{set:e=>t.current=e,get:()=>t.current,is:e=>t.current===e}})(),d=e=>a()&&l.is(e),g=(...e)=>{const t=n(...e);if(t instanceof Promise)return l.set(t),c.setLoading(),t.then(e=>{d(t)&&c.setResult(e)},e=>{d(t)&&c.setError(e)}),t;{const e=t;return c.setResult(e),Promise.resolve(e)}},v=!a();return t(()=>{v?i.executeOnMount&&g(...s):i.executeOnUpdate&&g(...s)},s),{...c.value,set:c.set,execute:g,currentPromise:l.get()}},u=(e,t,r)=>u(e,t,r),i=(t,r,n)=>{const o=e();return u(async(...e)=>{o.current&&o.current.abort();const r=new AbortController;o.current=r;try{return await t(r.signal,...e)}finally{o.current===r&&(o.current=void 0)}},r,n)},c=e=>s(e,[],{executeOnMount:!1,executeOnUpdate:!1,initialState:()=>({loading:!1,result:void 0,error:void 0})});export{u as useAsync,i as useAsyncAbortable,c as useAsyncCallback}; | ||
//# sourceMappingURL=react-async-hook.es.production.js.map |
@@ -63,3 +63,5 @@ (function (global, factory) { | ||
}; | ||
const useAsync = (asyncFunction, params, options) => { | ||
// Relaxed interface which accept both async and sync functions | ||
// Accepting sync function is convenient for useAsyncCallback | ||
const useAsyncInternal = (asyncFunction, params, options) => { | ||
const normalizedOptions = normalizeOptions(options); | ||
@@ -72,16 +74,24 @@ const AsyncState = useAsyncState(normalizedOptions); | ||
const shouldHandlePromise = (p) => isMounted() && CurrentPromise.is(p); | ||
const executeAsyncOperation = () => { | ||
const promise = asyncFunction(...params); | ||
CurrentPromise.set(promise); | ||
AsyncState.setLoading(); | ||
promise.then(result => { | ||
if (shouldHandlePromise(promise)) { | ||
AsyncState.setResult(result); | ||
} | ||
}, error => { | ||
if (shouldHandlePromise(promise)) { | ||
AsyncState.setError(error); | ||
} | ||
}); | ||
return promise; | ||
const executeAsyncOperation = (...args) => { | ||
const promise = asyncFunction(...args); | ||
if (promise instanceof Promise) { | ||
CurrentPromise.set(promise); | ||
AsyncState.setLoading(); | ||
promise.then(result => { | ||
if (shouldHandlePromise(promise)) { | ||
AsyncState.setResult(result); | ||
} | ||
}, error => { | ||
if (shouldHandlePromise(promise)) { | ||
AsyncState.setError(error); | ||
} | ||
}); | ||
return promise; | ||
} | ||
else { | ||
// We allow passing a non-async function (mostly for useAsyncCallback conveniency) | ||
const syncResult = promise; | ||
AsyncState.setResult(syncResult); | ||
return Promise.resolve(syncResult); | ||
} | ||
}; | ||
@@ -93,6 +103,6 @@ // Keep this outside useEffect, because inside isMounted() | ||
if (isMounting) { | ||
normalizedOptions.executeOnMount && executeAsyncOperation(); | ||
normalizedOptions.executeOnMount && executeAsyncOperation(...params); | ||
} | ||
else { | ||
normalizedOptions.executeOnUpdate && executeAsyncOperation(); | ||
normalizedOptions.executeOnUpdate && executeAsyncOperation(...params); | ||
} | ||
@@ -107,2 +117,3 @@ }, params); | ||
}; | ||
const useAsync = (asyncFunction, params, options) => useAsync(asyncFunction, params, options); | ||
const useAsyncAbortable = (asyncFunction, params, options) => { | ||
@@ -133,7 +144,22 @@ const abortControllerRef = react.useRef(); | ||
}; | ||
const useAsyncCallback = (asyncFunction) => { | ||
return useAsyncInternal(asyncFunction, | ||
// Hacky but in such case we don't need the params, | ||
// because async function is only executed manually | ||
[], { | ||
executeOnMount: false, | ||
executeOnUpdate: false, | ||
initialState: () => ({ | ||
loading: false, | ||
result: undefined, | ||
error: undefined, | ||
}), | ||
}); | ||
}; | ||
exports.useAsync = useAsync; | ||
exports.useAsyncAbortable = useAsyncAbortable; | ||
exports.useAsyncCallback = useAsyncCallback; | ||
})); | ||
//# sourceMappingURL=react-async-hook.umd.development.js.map |
@@ -1,2 +0,2 @@ | ||
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("react")):"function"==typeof define&&define.amd?define(["exports","react"],t):(e=e||self,t(e["react-async-hook"]={},e.React))}(this,function(e,t){"use strict";const r={loading:!0,result:void 0,error:void 0},n={initialState:()=>r,executeOnMount:!0,executeOnUpdate:!0,setLoading:e=>r,setResult:(e,t)=>({loading:!1,result:e,error:void 0}),setError:(e,t)=>({loading:!1,result:void 0,error:e})},s=(e,r,s)=>{const u=(e=>({...n,...e}))(s),o=(e=>{const[r,n]=t.useState(e.initialState);return{value:r,set:n,setLoading:()=>n(e.setLoading(r)),setResult:t=>n(e.setResult(t,r)),setError:t=>n(e.setError(t,r))}})(u),c=(()=>{const e=t.useRef(!1);return t.useEffect(()=>(e.current=!0,()=>{e.current=!1}),[]),()=>e.current})(),i=(()=>{const e=t.useRef(null);return{set:t=>e.current=t,get:()=>e.current,is:t=>e.current===t}})(),a=e=>c()&&i.is(e),l=()=>{const t=e(...r);return i.set(t),o.setLoading(),t.then(e=>{a(t)&&o.setResult(e)},e=>{a(t)&&o.setError(e)}),t},d=!c();return t.useEffect(()=>{d?u.executeOnMount&&l():u.executeOnUpdate&&l()},r),{...o.value,set:o.set,execute:l,currentPromise:i.get()}};e.useAsync=s,e.useAsyncAbortable=((e,r,n)=>{const u=t.useRef();return s(async(...t)=>{u.current&&u.current.abort();const r=new AbortController;u.current=r;try{return await e(r.signal,...t)}finally{u.current===r&&(u.current=void 0)}},r,n)})}); | ||
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("react")):"function"==typeof define&&define.amd?define(["exports","react"],t):(e=e||self,t(e["react-async-hook"]={},e.React))}(this,function(e,t){"use strict";const r={loading:!0,result:void 0,error:void 0},n={initialState:()=>r,executeOnMount:!0,executeOnUpdate:!0,setLoading:e=>r,setResult:(e,t)=>({loading:!1,result:e,error:void 0}),setError:(e,t)=>({loading:!1,result:void 0,error:e})},s=(e,r,s)=>{const o=(e=>({...n,...e}))(s),u=(e=>{const[r,n]=t.useState(e.initialState);return{value:r,set:n,setLoading:()=>n(e.setLoading(r)),setResult:t=>n(e.setResult(t,r)),setError:t=>n(e.setError(t,r))}})(o),c=(()=>{const e=t.useRef(!1);return t.useEffect(()=>(e.current=!0,()=>{e.current=!1}),[]),()=>e.current})(),i=(()=>{const e=t.useRef(null);return{set:t=>e.current=t,get:()=>e.current,is:t=>e.current===t}})(),a=e=>c()&&i.is(e),l=(...t)=>{const r=e(...t);if(r instanceof Promise)return i.set(r),u.setLoading(),r.then(e=>{a(r)&&u.setResult(e)},e=>{a(r)&&u.setError(e)}),r;{const e=r;return u.setResult(e),Promise.resolve(e)}},d=!c();return t.useEffect(()=>{d?o.executeOnMount&&l(...r):o.executeOnUpdate&&l(...r)},r),{...u.value,set:u.set,execute:l,currentPromise:i.get()}},o=(e,t,r)=>o(e,t,r);e.useAsync=o,e.useAsyncAbortable=((e,r,n)=>{const s=t.useRef();return o(async(...t)=>{s.current&&s.current.abort();const r=new AbortController;s.current=r;try{return await e(r.signal,...t)}finally{s.current===r&&(s.current=void 0)}},r,n)}),e.useAsyncCallback=(e=>s(e,[],{executeOnMount:!1,executeOnUpdate:!1,initialState:()=>({loading:!1,result:void 0,error:void 0})}))}); | ||
//# sourceMappingURL=react-async-hook.umd.production.js.map |
{ | ||
"name": "react-async-hook", | ||
"version": "2.3.0", | ||
"version": "3.0.0", | ||
"description": "Async hook", | ||
@@ -5,0 +5,0 @@ "author": "Sébastien Lorber", |
@@ -7,3 +7,3 @@ # React-async-hook | ||
- Simplest way to get async result in your React component | ||
- Very good, native, typescript support | ||
- Very good, native, Typescript support | ||
- Refetch on params change | ||
@@ -15,4 +15,14 @@ - Handle concurrency issues if params change too fast | ||
- Options to customize state updates | ||
- Handle async callbacks (mutations) | ||
## Usecase: loading async data into a component | ||
The ability to inject remote/async data into a React component is a very common React need. Later we might support Suspense as well. | ||
```tsx | ||
import { useAsync } from 'react-async-hook'; | ||
const fetchStarwarsHero = async id => | ||
(await fetch(`https://swapi.co/api/people/${id}/`)).json(); | ||
const StarwarsHero = ({ id }) => { | ||
@@ -35,19 +45,33 @@ const asyncHero = useAsync(fetchStarwarsHero, [id]); | ||
And the typesafe async function could be: | ||
## Usecase: injecting async feedback into buttons | ||
If you have a Todo app, you might want to show some feedback into the "create todo" button while the creation is pending, and prevent duplicate todo creations by disabling the button. | ||
Just wire `useAsyncCallback` to your `onClick` prop in your primitive `AppButton` component. The library will show a feedback only if the button onClick callback is async, otherwise it won't do anything. | ||
```tsx | ||
type StarwarsHero = { | ||
id: string; | ||
name: string; | ||
import { useAsyncCallback } from "react-async-hook"; | ||
const AppButton = ({ onClick, children }) => { | ||
const asyncOnClick = useAsyncCallback(onClick); | ||
return ( | ||
<button onClick={asyncOnClick.execute} disabled={asyncOnClick.loading}> | ||
{asyncOnClick.loading ? "..." : children} | ||
</div> | ||
); | ||
}; | ||
const fetchStarwarsHero = async (id: string): Promise<StarwarsHero> => { | ||
const result = await fetch(`https://swapi.co/api/people/${id}/`); | ||
if (result.status !== 200) { | ||
throw new Error('bad status = ' + result.status); | ||
} | ||
return result.json(); | ||
}; | ||
const CreateTodoButton = () => ( | ||
<AppButton | ||
onClick={async () => { | ||
await createTodoAPI("new todo text") | ||
}} | ||
> | ||
Create Todo | ||
</AppButton> | ||
) | ||
``` | ||
# Examples | ||
Examples are running on [this page](https://react-async-hook.netlify.com/) and [implemented here](https://github.com/slorber/react-async-hook/blob/master/example/index.tsx) (in Typescript) | ||
@@ -54,0 +78,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
79735
365
260