UNPKG

react-async

Version:

React component for declarative promise resolution and data fetching

174 lines (147 loc) 6.27 kB
function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; } function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function (key) { _defineProperty(target, key, source[key]); }); } return target; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } import { useCallback, useDebugValue, useEffect, useMemo, useRef, useReducer } from "react"; import { actionTypes, init, reducer } from "./reducer.js"; const noop = () => {}; const useAsync = (arg1, arg2) => { const counter = useRef(0); const isMounted = useRef(true); const lastArgs = useRef(undefined); const prevOptions = useRef(undefined); const abortController = useRef({ abort: noop }); const options = typeof arg1 === "function" ? _objectSpread({}, arg2, { promiseFn: arg1 }) : arg1; const { promise, promiseFn, deferFn, initialValue, onResolve, onReject, watch, watchFn } = options; const [state, dispatch] = useReducer(reducer, options, init); const setData = (data, callback = noop) => { if (isMounted.current) { dispatch({ type: actionTypes.fulfill, payload: data }); callback(); } return data; }; const setError = (error, callback = noop) => { if (isMounted.current) { dispatch({ type: actionTypes.reject, payload: error, error: true }); callback(); } return error; }; const handleResolve = count => data => count === counter.current && setData(data, () => onResolve && onResolve(data)); const handleReject = count => error => count === counter.current && setError(error, () => onReject && onReject(error)); const start = () => { if ("AbortController" in window) { abortController.current.abort(); abortController.current = new window.AbortController(); } counter.current++; isMounted.current && dispatch({ type: actionTypes.start, meta: { counter: counter.current } }); }; const load = () => { if (promise) { start(); return promise.then(handleResolve(counter.current), handleReject(counter.current)); } const isPreInitialized = initialValue && counter.current === 0; if (promiseFn && !isPreInitialized) { start(); return promiseFn(options, abortController.current).then(handleResolve(counter.current), handleReject(counter.current)); } }; const run = (...args) => { if (deferFn) { lastArgs.current = args; start(); return deferFn(args, options, abortController.current).then(handleResolve(counter.current), handleReject(counter.current)); } }; const cancel = () => { counter.current++; abortController.current.abort(); isMounted.current && dispatch({ type: actionTypes.cancel, meta: { counter: counter.current } }); }; useEffect(() => { if (watchFn && prevOptions.current && watchFn(options, prevOptions.current)) load(); }); useEffect(() => { promise || promiseFn ? load() : cancel(); }, [promise, promiseFn, watch]); useEffect(() => () => isMounted.current = false, []); useEffect(() => () => abortController.current.abort(), []); useEffect(() => (prevOptions.current = options) && undefined); useDebugValue(state, ({ status }) => `[${counter.current}] ${status}`); return useMemo(() => _objectSpread({}, state, { cancel, run, reload: () => lastArgs.current ? run(...lastArgs.current) : load(), setData, setError }), [state, deferFn, onResolve, onReject]); }; const parseResponse = (accept, json) => res => { if (!res.ok) return Promise.reject(res); if (json === false) return res; if (json === true || accept === "application/json") return res.json(); return res; }; const useAsyncFetch = (input, init, _ref = {}) => { let { defer, json } = _ref, options = _objectWithoutProperties(_ref, ["defer", "json"]); const method = input.method || init && init.method; const headers = input.headers || init && init.headers || {}; const accept = headers["Accept"] || headers["accept"] || headers.get && headers.get("accept"); const doFetch = (input, init) => window.fetch(input, init).then(parseResponse(accept, json)); const isDefer = defer === true || ~["POST", "PUT", "PATCH", "DELETE"].indexOf(method); const fn = defer === false || !isDefer ? "promiseFn" : "deferFn"; const state = useAsync(_objectSpread({}, options, { [fn]: useCallback((_, props, ctrl) => doFetch(input, _objectSpread({ signal: ctrl ? ctrl.signal : props.signal }, init)), [JSON.stringify(input), JSON.stringify(init)]) })); useDebugValue(state, ({ counter, status }) => `[${counter}] ${status}`); return state; }; const unsupported = () => { throw new Error("useAsync requires React v16.8 or up. Upgrade your React version or use the <Async> component instead."); }; export default useEffect ? useAsync : unsupported; export const useFetch = useEffect ? useAsyncFetch : unsupported;