react-async-hook
Advanced tools
Comparing version 3.6.2 to 4.0.0
@@ -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
605
360