react-async-hook
Advanced tools
Comparing version
@@ -43,14 +43,2 @@ declare type UnknownResult = unknown; | ||
export declare const useAsyncCallback: <R = unknown, Args extends any[] = any[]>(asyncFunction: (...args: Args) => MaybePromise<R>, options?: UseAsyncCallbackOptions<R>) => UseAsyncReturn<R, Args>; | ||
export declare const useAsyncFetchMore: <R, Args extends any[]>({ value, fetchMore, merge, isEnd: isEndFn, }: { | ||
value: UseAsyncReturn<R, Args>; | ||
fetchMore: (result: R) => Promise<R>; | ||
merge: (result: R, moreResult: R) => R; | ||
isEnd: (moreResult: R) => boolean; | ||
}) => { | ||
canFetchMore: boolean; | ||
loading: boolean; | ||
status: AsyncStateStatus; | ||
fetchMore: () => Promise<R>; | ||
isEnd: boolean; | ||
}; | ||
export {}; |
@@ -42,5 +42,6 @@ 'use strict'; | ||
var useIsomorphicLayoutEffect = typeof window !== 'undefined' && typeof window.document !== 'undefined' && typeof window.document.createElement !== 'undefined' ? react.useLayoutEffect : react.useEffect; // assign current value to a ref and providing a getter. | ||
var useIsomorphicLayoutEffect = typeof window !== 'undefined' && typeof window.document !== 'undefined' && typeof window.document.createElement !== 'undefined' ? react.useLayoutEffect : react.useEffect; // Assign current value to a ref and returns a stable getter to get the latest value. | ||
// This way we are sure to always get latest value provided to hook and | ||
// avoid weird issues due to closures capturing stale values... | ||
// See https://github.com/facebook/react/issues/16956 | ||
// See https://overreacted.io/making-setinterval-declarative-with-react-hooks/ | ||
@@ -53,5 +54,5 @@ | ||
}); | ||
return function () { | ||
return react.useCallback(function () { | ||
return ref.current; | ||
}; | ||
}, [ref]); | ||
}; | ||
@@ -132,9 +133,8 @@ | ||
}, [value, setValue]); | ||
var set = setValue; | ||
var merge = react.useCallback(function (state) { | ||
return set(_extends({}, value, {}, state)); | ||
}, [value, set]); | ||
return setValue(_extends({}, value, {}, state)); | ||
}, [value, setValue]); | ||
return { | ||
value: value, | ||
set: set, | ||
set: setValue, | ||
merge: merge, | ||
@@ -179,2 +179,6 @@ reset: reset, | ||
var useAsyncInternal = function useAsyncInternal(asyncFunction, params, options) { | ||
// Fallback missing params, only for JS users forgetting the deps array, to prevent infinite loops | ||
// https://github.com/slorber/react-async-hook/issues/27 | ||
// @ts-ignore | ||
!params && (params = []); | ||
var normalizedOptions = normalizeOptions(options); | ||
@@ -200,47 +204,54 @@ | ||
var promise = asyncFunction.apply(void 0, args); | ||
// async ensures errors thrown synchronously are caught (ie, bug when formatting api payloads) | ||
// async ensures promise-like and synchronous functions are handled correctly too | ||
// see https://github.com/slorber/react-async-hook/issues/24 | ||
var promise = function () { | ||
try { | ||
return Promise.resolve(asyncFunction.apply(void 0, args)); | ||
} catch (e) { | ||
return Promise.reject(e); | ||
} | ||
}(); | ||
setCurrentParams(args); | ||
CurrentPromise.set(promise); | ||
AsyncState.setLoading(); | ||
promise.then(function (result) { | ||
if (shouldHandlePromise(promise)) { | ||
AsyncState.setResult(result); | ||
} | ||
if (promise instanceof Promise) { | ||
CurrentPromise.set(promise); | ||
AsyncState.setLoading(); | ||
promise.then(function (result) { | ||
if (shouldHandlePromise(promise)) { | ||
AsyncState.setResult(result); | ||
normalizedOptions.onSuccess(result, { | ||
isCurrent: function isCurrent() { | ||
return CurrentPromise.is(promise); | ||
} | ||
}); | ||
}, function (error) { | ||
if (shouldHandlePromise(promise)) { | ||
AsyncState.setError(error); | ||
} | ||
normalizedOptions.onSuccess(result, { | ||
isCurrent: function isCurrent() { | ||
return CurrentPromise.is(promise); | ||
} | ||
}); | ||
}, function (error) { | ||
if (shouldHandlePromise(promise)) { | ||
AsyncState.setError(error); | ||
normalizedOptions.onError(error, { | ||
isCurrent: function isCurrent() { | ||
return CurrentPromise.is(promise); | ||
} | ||
}); | ||
}); | ||
return promise; | ||
}; | ||
normalizedOptions.onError(error, { | ||
isCurrent: function isCurrent() { | ||
return CurrentPromise.is(promise); | ||
} | ||
}); | ||
}); | ||
return promise; | ||
} else { | ||
// We allow passing a non-async function (mostly for useAsyncCallback conveniency) | ||
var syncResult = promise; | ||
AsyncState.setResult(syncResult); | ||
return Promise.resolve(syncResult); | ||
} | ||
}; // Keep this outside useEffect, because inside isMounted() | ||
var getLatestExecuteAsyncOperation = useGetter(executeAsyncOperation); | ||
var executeAsyncOperationMemo = react.useCallback(function () { | ||
return getLatestExecuteAsyncOperation().apply(void 0, arguments); | ||
}, [getLatestExecuteAsyncOperation]); // Keep this outside useEffect, because inside isMounted() | ||
// will be true as the component is already mounted when it's run | ||
var isMounting = !isMounted(); | ||
react.useEffect(function () { | ||
if (isMounting) { | ||
normalizedOptions.executeOnMount && executeAsyncOperation.apply(void 0, params); | ||
} else { | ||
normalizedOptions.executeOnUpdate && executeAsyncOperation.apply(void 0, params); | ||
} | ||
var execute = function execute() { | ||
return getLatestExecuteAsyncOperation().apply(void 0, params); | ||
}; | ||
isMounting && normalizedOptions.executeOnMount && execute(); | ||
!isMounting && normalizedOptions.executeOnUpdate && execute(); | ||
}, params); | ||
@@ -251,3 +262,3 @@ return _extends({}, AsyncState.value, { | ||
reset: AsyncState.reset, | ||
execute: executeAsyncOperation, | ||
execute: executeAsyncOperationMemo, | ||
currentPromise: CurrentPromise.get(), | ||
@@ -306,81 +317,6 @@ currentParams: currentParams | ||
}; | ||
var useAsyncFetchMore = function useAsyncFetchMore(_ref) { | ||
var value = _ref.value, | ||
fetchMore = _ref.fetchMore, | ||
merge = _ref.merge, | ||
isEndFn = _ref.isEnd; | ||
var getAsyncValue = useGetter(value); | ||
var _useState3 = react.useState(false), | ||
isEnd = _useState3[0], | ||
setIsEnd = _useState3[1]; // TODO not really fan of this id thing, we should find a way to support cancellation! | ||
var fetchMoreId = react.useRef(0); | ||
var fetchMoreAsync = useAsyncCallback(function () { | ||
try { | ||
var freshAsyncValue = getAsyncValue(); | ||
if (freshAsyncValue.status !== 'success') { | ||
throw new Error("Can't fetch more if the original fetch is not a success"); | ||
} | ||
if (fetchMoreAsync.status === 'loading') { | ||
throw new Error("Can't fetch more, because we are already fetching more!"); | ||
} | ||
fetchMoreId.current = fetchMoreId.current + 1; | ||
var currentId = fetchMoreId.current; | ||
return Promise.resolve(fetchMore(freshAsyncValue.result)).then(function (moreResult) { | ||
// TODO not satisfied with this, we should just use "freshAsyncValue === getAsyncValue()" but asyncValue is not "stable" | ||
var isStillSameValue = freshAsyncValue.status === getAsyncValue().status && freshAsyncValue.result === getAsyncValue().result; | ||
var isStillSameId = fetchMoreId.current === currentId; // Handle race conditions: we only merge the fetchMore result if the initial async value is the same | ||
var canMerge = isStillSameValue && isStillSameId; | ||
if (canMerge) { | ||
value.merge({ | ||
result: merge(value.result, moreResult) | ||
}); | ||
if (isEndFn(moreResult)) { | ||
setIsEnd(true); | ||
} | ||
} // return is useful for chaining, like fetchMore().then(result => {}); | ||
return moreResult; | ||
}); | ||
} catch (e) { | ||
return Promise.reject(e); | ||
} | ||
}); | ||
var reset = function reset() { | ||
fetchMoreAsync.reset(); | ||
setIsEnd(false); | ||
}; // We only allow to fetch more on a stable async value | ||
// If that value change for whatever reason, we reset the fetchmore too (which will make current pending requests to be ignored) | ||
// TODO value is not stable, we could just reset on value change otherwise | ||
var shouldReset = value.status !== 'success'; | ||
react.useEffect(function () { | ||
if (shouldReset) { | ||
reset(); | ||
} | ||
}, [shouldReset]); | ||
return { | ||
canFetchMore: value.status === 'success' && fetchMoreAsync.status !== 'loading', | ||
loading: fetchMoreAsync.loading, | ||
status: fetchMoreAsync.status, | ||
fetchMore: fetchMoreAsync.execute, | ||
isEnd: isEnd | ||
}; | ||
}; | ||
exports.useAsync = useAsync; | ||
exports.useAsyncAbortable = useAsyncAbortable; | ||
exports.useAsyncCallback = useAsyncCallback; | ||
exports.useAsyncFetchMore = useAsyncFetchMore; | ||
//# sourceMappingURL=react-async-hook.cjs.development.js.map |
@@ -1,2 +0,2 @@ | ||
"use strict";var e=require("react");function t(){return(t=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var r=arguments[t];for(var n in r)Object.prototype.hasOwnProperty.call(r,n)&&(e[n]=r[n])}return e}).apply(this,arguments)}"undefined"!=typeof Symbol&&(Symbol.iterator||(Symbol.iterator=Symbol("Symbol.iterator"))),"undefined"!=typeof Symbol&&(Symbol.asyncIterator||(Symbol.asyncIterator=Symbol("Symbol.asyncIterator")));var r="undefined"!=typeof window&&void 0!==window.document&&void 0!==window.document.createElement?e.useLayoutEffect:e.useEffect,n={status:"not-requested",loading:!1,result:void 0,error:void 0},u={status:"loading",loading:!0,result:void 0,error:void 0},o=function(){},s={initialState:function(e){return e&&e.executeOnMount?u:n},executeOnMount:!0,executeOnUpdate:!0,setLoading:function(e){return u},setResult:function(e,t){return{status:"success",loading:!1,result:e,error:void 0}},setError:function(e,t){return{status:"error",loading:!1,result:void 0,error:e}},onSuccess:o,onError:o},c=function(r,n,u){var o,c=function(e){return t({},s,{},e)}(u),i=e.useState(null),a=i[0],l=i[1],f=function(r){var n=e.useState(function(){return r.initialState(r)}),u=n[0],o=n[1],s=e.useCallback(function(){return o(r.initialState(r))},[o,r]),c=e.useCallback(function(){return o(r.setLoading(u))},[u,o]),i=e.useCallback(function(e){return o(r.setResult(e,u))},[u,o]),a=e.useCallback(function(e){return o(r.setError(e,u))},[u,o]),l=o,f=e.useCallback(function(e){return l(t({},u,{},e))},[u,l]);return{value:u,set:l,merge:f,reset:s,setLoading:c,setResult:i,setError:a}}(c),d=(o=e.useRef(!1),e.useEffect(function(){return o.current=!0,function(){o.current=!1}},[]),function(){return o.current}),v=function(){var t=e.useRef(null);return{set:function(e){return t.current=e},get:function(){return t.current},is:function(e){return t.current===e}}}(),y=function(e){return d()&&v.is(e)},m=function(){for(var e=arguments.length,t=new Array(e),n=0;n<e;n++)t[n]=arguments[n];var u=r.apply(void 0,t);if(l(t),u instanceof Promise)return v.set(u),f.setLoading(),u.then(function(e){y(u)&&f.setResult(e),c.onSuccess(e,{isCurrent:function(){return v.is(u)}})},function(e){y(u)&&f.setError(e),c.onError(e,{isCurrent:function(){return v.is(u)}})}),u;var o=u;return f.setResult(o),Promise.resolve(o)},g=!d();return e.useEffect(function(){g?c.executeOnMount&&m.apply(void 0,n):c.executeOnUpdate&&m.apply(void 0,n)},n),t({},f.value,{set:f.set,merge:f.merge,reset:f.reset,execute:m,currentPromise:v.get(),currentParams:a})};function i(e,t,r){return c(e,t,r)}var a=function(e,r){return c(e,[],t({},r,{executeOnMount:!1,executeOnUpdate:!1}))};exports.useAsync=i,exports.useAsyncAbortable=function(t,r,n){var u=e.useRef();return i(function(){for(var e=arguments.length,r=new Array(e),n=0;n<e;n++)r[n]=arguments[n];try{u.current&&u.current.abort();var o=new AbortController;return u.current=o,Promise.resolve(function(e,n){try{var u=Promise.resolve(t.apply(void 0,[o.signal].concat(r)))}catch(e){return n(!0,e)}return u&&u.then?u.then(n.bind(null,!1),n.bind(null,!0)):n(!1,value)}(0,function(e,t){if(u.current===o&&(u.current=void 0),e)throw t;return t}))}catch(e){return Promise.reject(e)}},r,n)},exports.useAsyncCallback=a,exports.useAsyncFetchMore=function(t){var n,u,o=t.value,s=t.fetchMore,c=t.merge,i=t.isEnd,l=(u=e.useRef(n=o),r(function(){u.current=n}),function(){return u.current}),f=e.useState(!1),d=f[0],v=f[1],y=e.useRef(0),m=a(function(){try{var e=l();if("success"!==e.status)throw new Error("Can't fetch more if the original fetch is not a success");if("loading"===m.status)throw new Error("Can't fetch more, because we are already fetching more!");y.current=y.current+1;var t=y.current;return Promise.resolve(s(e.result)).then(function(r){return e.status===l().status&&e.result===l().result&&y.current===t&&(o.merge({result:c(o.result,r)}),i(r)&&v(!0)),r})}catch(e){return Promise.reject(e)}}),g="success"!==o.status;return e.useEffect(function(){g&&(m.reset(),v(!1))},[g]),{canFetchMore:"success"===o.status&&"loading"!==m.status,loading:m.loading,status:m.status,fetchMore:m.execute,isEnd:d}}; | ||
"use strict";var e=require("react");function r(){return(r=Object.assign||function(e){for(var r=1;r<arguments.length;r++){var t=arguments[r];for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n])}return e}).apply(this,arguments)}"undefined"!=typeof Symbol&&(Symbol.iterator||(Symbol.iterator=Symbol("Symbol.iterator"))),"undefined"!=typeof Symbol&&(Symbol.asyncIterator||(Symbol.asyncIterator=Symbol("Symbol.asyncIterator")));var t="undefined"!=typeof window&&void 0!==window.document&&void 0!==window.document.createElement?e.useLayoutEffect:e.useEffect,n={status:"not-requested",loading:!1,result:void 0,error:void 0},u={status:"loading",loading:!0,result:void 0,error:void 0},o=function(){},c={initialState:function(e){return e&&e.executeOnMount?u:n},executeOnMount:!0,executeOnUpdate:!0,setLoading:function(e){return u},setResult:function(e,r){return{status:"success",loading:!1,result:e,error:void 0}},setError:function(e,r){return{status:"error",loading:!1,result:void 0,error:e}},onSuccess:o,onError:o},i=function(n,u,o){!u&&(u=[]);var i,s=function(e){return r({},c,{},e)}(o),a=e.useState(null),l=a[0],f=a[1],d=function(t){var n=e.useState(function(){return t.initialState(t)}),u=n[0],o=n[1],c=e.useCallback(function(){return o(t.initialState(t))},[o,t]),i=e.useCallback(function(){return o(t.setLoading(u))},[u,o]),s=e.useCallback(function(e){return o(t.setResult(e,u))},[u,o]),a=e.useCallback(function(e){return o(t.setError(e,u))},[u,o]),l=e.useCallback(function(e){return o(r({},u,{},e))},[u,o]);return{value:u,set:o,merge:l,reset:c,setLoading:i,setResult:s,setError:a}}(s),v=(i=e.useRef(!1),e.useEffect(function(){return i.current=!0,function(){i.current=!1}},[]),function(){return i.current}),y=function(){var r=e.useRef(null);return{set:function(e){return r.current=e},get:function(){return r.current},is:function(e){return r.current===e}}}(),b=function(e){return v()&&y.is(e)},m=function(r){var n=e.useRef(r);return t(function(){n.current=r}),e.useCallback(function(){return n.current},[n])}(function(){for(var e=arguments.length,r=new Array(e),t=0;t<e;t++)r[t]=arguments[t];var u=function(){try{return Promise.resolve(n.apply(void 0,r))}catch(e){return Promise.reject(e)}}();return f(r),y.set(u),d.setLoading(),u.then(function(e){b(u)&&d.setResult(e),s.onSuccess(e,{isCurrent:function(){return y.is(u)}})},function(e){b(u)&&d.setError(e),s.onError(e,{isCurrent:function(){return y.is(u)}})}),u}),p=e.useCallback(function(){return m().apply(void 0,arguments)},[m]),g=!v();return e.useEffect(function(){var e=function(){return m().apply(void 0,u)};g&&s.executeOnMount&&e(),!g&&s.executeOnUpdate&&e()},u),r({},d.value,{set:d.set,merge:d.merge,reset:d.reset,execute:p,currentPromise:y.get(),currentParams:l})};function s(e,r,t){return i(e,r,t)}exports.useAsync=s,exports.useAsyncAbortable=function(r,t,n){var u=e.useRef();return s(function(){for(var e=arguments.length,t=new Array(e),n=0;n<e;n++)t[n]=arguments[n];try{u.current&&u.current.abort();var o=new AbortController;return u.current=o,Promise.resolve(function(e,n){try{var u=Promise.resolve(r.apply(void 0,[o.signal].concat(t)))}catch(e){return n(!0,e)}return u&&u.then?u.then(n.bind(null,!1),n.bind(null,!0)):n(!1,value)}(0,function(e,r){if(u.current===o&&(u.current=void 0),e)throw r;return r}))}catch(e){return Promise.reject(e)}},t,n)},exports.useAsyncCallback=function(e,t){return i(e,[],r({},t,{executeOnMount:!1,executeOnUpdate:!1}))}; | ||
//# sourceMappingURL=react-async-hook.cjs.production.min.js.map |
@@ -40,5 +40,6 @@ import { useLayoutEffect, useEffect, useRef, useState, useCallback } from 'react'; | ||
var useIsomorphicLayoutEffect = typeof window !== 'undefined' && typeof window.document !== 'undefined' && typeof window.document.createElement !== 'undefined' ? useLayoutEffect : useEffect; // assign current value to a ref and providing a getter. | ||
var useIsomorphicLayoutEffect = typeof window !== 'undefined' && typeof window.document !== 'undefined' && typeof window.document.createElement !== 'undefined' ? useLayoutEffect : useEffect; // Assign current value to a ref and returns a stable getter to get the latest value. | ||
// This way we are sure to always get latest value provided to hook and | ||
// avoid weird issues due to closures capturing stale values... | ||
// See https://github.com/facebook/react/issues/16956 | ||
// See https://overreacted.io/making-setinterval-declarative-with-react-hooks/ | ||
@@ -51,5 +52,5 @@ | ||
}); | ||
return function () { | ||
return useCallback(function () { | ||
return ref.current; | ||
}; | ||
}, [ref]); | ||
}; | ||
@@ -130,9 +131,8 @@ | ||
}, [value, setValue]); | ||
var set = setValue; | ||
var merge = useCallback(function (state) { | ||
return set(_extends({}, value, {}, state)); | ||
}, [value, set]); | ||
return setValue(_extends({}, value, {}, state)); | ||
}, [value, setValue]); | ||
return { | ||
value: value, | ||
set: set, | ||
set: setValue, | ||
merge: merge, | ||
@@ -177,2 +177,6 @@ reset: reset, | ||
var useAsyncInternal = function useAsyncInternal(asyncFunction, params, options) { | ||
// Fallback missing params, only for JS users forgetting the deps array, to prevent infinite loops | ||
// https://github.com/slorber/react-async-hook/issues/27 | ||
// @ts-ignore | ||
!params && (params = []); | ||
var normalizedOptions = normalizeOptions(options); | ||
@@ -198,47 +202,54 @@ | ||
var promise = asyncFunction.apply(void 0, args); | ||
// async ensures errors thrown synchronously are caught (ie, bug when formatting api payloads) | ||
// async ensures promise-like and synchronous functions are handled correctly too | ||
// see https://github.com/slorber/react-async-hook/issues/24 | ||
var promise = function () { | ||
try { | ||
return Promise.resolve(asyncFunction.apply(void 0, args)); | ||
} catch (e) { | ||
return Promise.reject(e); | ||
} | ||
}(); | ||
setCurrentParams(args); | ||
CurrentPromise.set(promise); | ||
AsyncState.setLoading(); | ||
promise.then(function (result) { | ||
if (shouldHandlePromise(promise)) { | ||
AsyncState.setResult(result); | ||
} | ||
if (promise instanceof Promise) { | ||
CurrentPromise.set(promise); | ||
AsyncState.setLoading(); | ||
promise.then(function (result) { | ||
if (shouldHandlePromise(promise)) { | ||
AsyncState.setResult(result); | ||
normalizedOptions.onSuccess(result, { | ||
isCurrent: function isCurrent() { | ||
return CurrentPromise.is(promise); | ||
} | ||
}); | ||
}, function (error) { | ||
if (shouldHandlePromise(promise)) { | ||
AsyncState.setError(error); | ||
} | ||
normalizedOptions.onSuccess(result, { | ||
isCurrent: function isCurrent() { | ||
return CurrentPromise.is(promise); | ||
} | ||
}); | ||
}, function (error) { | ||
if (shouldHandlePromise(promise)) { | ||
AsyncState.setError(error); | ||
normalizedOptions.onError(error, { | ||
isCurrent: function isCurrent() { | ||
return CurrentPromise.is(promise); | ||
} | ||
}); | ||
}); | ||
return promise; | ||
}; | ||
normalizedOptions.onError(error, { | ||
isCurrent: function isCurrent() { | ||
return CurrentPromise.is(promise); | ||
} | ||
}); | ||
}); | ||
return promise; | ||
} else { | ||
// We allow passing a non-async function (mostly for useAsyncCallback conveniency) | ||
var syncResult = promise; | ||
AsyncState.setResult(syncResult); | ||
return Promise.resolve(syncResult); | ||
} | ||
}; // Keep this outside useEffect, because inside isMounted() | ||
var getLatestExecuteAsyncOperation = useGetter(executeAsyncOperation); | ||
var executeAsyncOperationMemo = useCallback(function () { | ||
return getLatestExecuteAsyncOperation().apply(void 0, arguments); | ||
}, [getLatestExecuteAsyncOperation]); // Keep this outside useEffect, because inside isMounted() | ||
// will be true as the component is already mounted when it's run | ||
var isMounting = !isMounted(); | ||
useEffect(function () { | ||
if (isMounting) { | ||
normalizedOptions.executeOnMount && executeAsyncOperation.apply(void 0, params); | ||
} else { | ||
normalizedOptions.executeOnUpdate && executeAsyncOperation.apply(void 0, params); | ||
} | ||
var execute = function execute() { | ||
return getLatestExecuteAsyncOperation().apply(void 0, params); | ||
}; | ||
isMounting && normalizedOptions.executeOnMount && execute(); | ||
!isMounting && normalizedOptions.executeOnUpdate && execute(); | ||
}, params); | ||
@@ -249,3 +260,3 @@ return _extends({}, AsyncState.value, { | ||
reset: AsyncState.reset, | ||
execute: executeAsyncOperation, | ||
execute: executeAsyncOperationMemo, | ||
currentPromise: CurrentPromise.get(), | ||
@@ -304,78 +315,4 @@ currentParams: currentParams | ||
}; | ||
var useAsyncFetchMore = function useAsyncFetchMore(_ref) { | ||
var value = _ref.value, | ||
fetchMore = _ref.fetchMore, | ||
merge = _ref.merge, | ||
isEndFn = _ref.isEnd; | ||
var getAsyncValue = useGetter(value); | ||
var _useState3 = useState(false), | ||
isEnd = _useState3[0], | ||
setIsEnd = _useState3[1]; // TODO not really fan of this id thing, we should find a way to support cancellation! | ||
var fetchMoreId = useRef(0); | ||
var fetchMoreAsync = useAsyncCallback(function () { | ||
try { | ||
var freshAsyncValue = getAsyncValue(); | ||
if (freshAsyncValue.status !== 'success') { | ||
throw new Error("Can't fetch more if the original fetch is not a success"); | ||
} | ||
if (fetchMoreAsync.status === 'loading') { | ||
throw new Error("Can't fetch more, because we are already fetching more!"); | ||
} | ||
fetchMoreId.current = fetchMoreId.current + 1; | ||
var currentId = fetchMoreId.current; | ||
return Promise.resolve(fetchMore(freshAsyncValue.result)).then(function (moreResult) { | ||
// TODO not satisfied with this, we should just use "freshAsyncValue === getAsyncValue()" but asyncValue is not "stable" | ||
var isStillSameValue = freshAsyncValue.status === getAsyncValue().status && freshAsyncValue.result === getAsyncValue().result; | ||
var isStillSameId = fetchMoreId.current === currentId; // Handle race conditions: we only merge the fetchMore result if the initial async value is the same | ||
var canMerge = isStillSameValue && isStillSameId; | ||
if (canMerge) { | ||
value.merge({ | ||
result: merge(value.result, moreResult) | ||
}); | ||
if (isEndFn(moreResult)) { | ||
setIsEnd(true); | ||
} | ||
} // return is useful for chaining, like fetchMore().then(result => {}); | ||
return moreResult; | ||
}); | ||
} catch (e) { | ||
return Promise.reject(e); | ||
} | ||
}); | ||
var reset = function reset() { | ||
fetchMoreAsync.reset(); | ||
setIsEnd(false); | ||
}; // We only allow to fetch more on a stable async value | ||
// If that value change for whatever reason, we reset the fetchmore too (which will make current pending requests to be ignored) | ||
// TODO value is not stable, we could just reset on value change otherwise | ||
var shouldReset = value.status !== 'success'; | ||
useEffect(function () { | ||
if (shouldReset) { | ||
reset(); | ||
} | ||
}, [shouldReset]); | ||
return { | ||
canFetchMore: value.status === 'success' && fetchMoreAsync.status !== 'loading', | ||
loading: fetchMoreAsync.loading, | ||
status: fetchMoreAsync.status, | ||
fetchMore: fetchMoreAsync.execute, | ||
isEnd: isEnd | ||
}; | ||
}; | ||
export { useAsync, useAsyncAbortable, useAsyncCallback, useAsyncFetchMore }; | ||
export { useAsync, useAsyncAbortable, useAsyncCallback }; | ||
//# sourceMappingURL=react-async-hook.esm.js.map |
{ | ||
"name": "react-async-hook", | ||
"version": "3.6.2", | ||
"version": "4.0.0", | ||
"description": "Async hook", | ||
@@ -5,0 +5,0 @@ "author": "Sébastien Lorber", |
@@ -6,5 +6,5 @@ # React-async-hook | ||
This library only **does one small thing**, and **does it well**. | ||
This **tiny** library only **does one thing**, and **does it well**. | ||
Don't expect it to grow in size, because it is **feature complete**: | ||
Don't expect it to grow in size, it is **feature complete**: | ||
@@ -14,3 +14,3 @@ - Handle fetches (`useAsync`) | ||
- Handle cancellation (`useAsyncAbortable` + `AbortController`) | ||
- Handle race conditions | ||
- Handle [race conditions](https://sebastienlorber.com/handling-api-request-race-conditions-in-react) | ||
- Platform agnostic | ||
@@ -28,26 +28,16 @@ - Works with any async function, not just backend API calls, not just fetch/axios... | ||
- Tiny (1.5k minified gzipped) | ||
- At least 3 times smaller than popular alternatives. | ||
- CommonJS + ESM bundles, tree-shakable | ||
- Design using composition, good tree-shakeability | ||
- Tiny | ||
- Way smaller than popular alternatives | ||
- CommonJS + ESM bundles | ||
- Tree-shakable | ||
**react-async-hook**: | ||
| Lib | min | min.gz | | ||
| -------------------- | ---------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | | ||
| **React-Async-Hook** | [](https://bundlephobia.com/package/react-async-hook) | [](https://bundlephobia.com/package/react-async-hook) | | ||
| **SWR** | [](https://bundlephobia.com/package/swr) | [](https://bundlephobia.com/package/swr) | | ||
| **React-Query** | [](https://bundlephobia.com/package/react-query) | [](https://bundlephobia.com/package/react-query) | | ||
| **React-Async** | [](https://bundlephobia.com/package/react-async) | [](https://bundlephobia.com/package/react-async) | | ||
| **Use-HTTP** | [](https://bundlephobia.com/package/use-http) | [](https://bundlephobia.com/package/use-http) | | ||
| **Rest-Hooks** | [](https://bundlephobia.com/package/rest-hooks) | [](https://bundlephobia.com/package/rest-hooks) | | ||
-  | ||
-  | ||
**React-Query**: | ||
-  | ||
-  | ||
**SWR**: | ||
-  | ||
-  | ||
## Things we don't support (by design): | ||
@@ -65,9 +55,6 @@ | ||
You can indeed build on top of this little lib to provide more advanced features, if you like composition, that is encouraged in the React ecosystem. | ||
You can build on top of this little lib to provide more advanced features (using composition), or move to popular full-featured libraries like [SWR](https://github.com/vercel/swr) or [React-Query](https://github.com/tannerlinsley/react-query). | ||
If you prefer a full-featured fetching library, try [SWR](https://github.com/vercel/swr) or [React-Query](https://github.com/tannerlinsley/react-query). | ||
## Use-case: loading async data into a component | ||
## 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. | ||
@@ -79,3 +66,3 @@ | ||
const fetchStarwarsHero = async id => | ||
(await fetch(`https://swapi.co/api/people/${id}/`)).json(); | ||
(await fetch(`https://swapi.dev/api/people/${id}/`)).json(); | ||
@@ -99,3 +86,3 @@ const StarwarsHero = ({ id }) => { | ||
## Usecase: injecting async feedback into buttons | ||
## Use-case: injecting async feedback into buttons | ||
@@ -156,7 +143,6 @@ 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. | ||
], | ||
} | ||
} | ||
}, | ||
}; | ||
``` | ||
# FAQ | ||
@@ -168,3 +154,3 @@ | ||
I recommend [awesome-debounce-promise](https://github.com/slorber/awesome-debounce-promise), as it handles nicely potential concurrency issues and have React in mind (particularly the common usecase of a debounced search input/autocomplete) | ||
I recommend [awesome-debounce-promise](https://github.com/slorber/awesome-debounce-promise), as it handles nicely potential concurrency issues and have React in mind (particularly the common use-case of a debounced search input/autocomplete) | ||
@@ -189,3 +175,3 @@ As debounced functions are stateful, we have to "store" the debounced function inside a component. We'll use for that [use-constant](https://github.com/Andarist/use-constant) (backed by `useRef`). | ||
This is one of the most common usecase for fetching data + debouncing in a component, and can be implemented easily by composing different libraries. | ||
This is one of the most common use-case for fetching data + debouncing in a component, and can be implemented easily by composing different libraries. | ||
All this logic can easily be extracted into a single hook that you can reuse. Here is an example: | ||
@@ -199,3 +185,3 @@ | ||
const result = await fetch( | ||
`https://swapi.co/api/people/?search=${encodeURIComponent(text)}`, | ||
`https://swapi.dev/api/people/?search=${encodeURIComponent(text)}`, | ||
{ | ||
@@ -283,3 +269,3 @@ signal: abortSignal, | ||
async (abortSignal, id) => { | ||
const result = await fetch(`https://swapi.co/api/people/${id}/`, { | ||
const result = await fetch(`https://swapi.dev/api/people/${id}/`, { | ||
signal: abortSignal, | ||
@@ -336,3 +322,6 @@ }); | ||
```tsx | ||
const asyncSomething = useAsync(() => fetchSomething(state.a), [state.a,state.b]); | ||
const asyncSomething = useAsync(() => fetchSomething(state.a), [ | ||
state.a, | ||
state.b, | ||
]); | ||
``` | ||
@@ -343,3 +332,5 @@ | ||
```tsx | ||
const asyncSomething = useAsync(() => fetchSomething(state.a, state.b), [state.a]); | ||
const asyncSomething = useAsync(() => fetchSomething(state.a, state.b), [ | ||
state.a, | ||
]); | ||
``` | ||
@@ -370,6 +361,5 @@ | ||
#### How to support retry? | ||
Use a lib that simply adds retry feature to async/promises directly. Doesn't exist? Build it. | ||
Use a lib that adds retry feature to async/promises directly. | ||
@@ -376,0 +366,0 @@ # License |
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
163107
-7.64%605
-15.97%360
-2.7%