@hyper-fetch/react
Version:
React hooks and utils for the hyper-fetch
194 lines (166 loc) • 5.95 kB
text/typescript
import { RequestInstance, getRequestDispatcher, QueueItemType, QueueDataType } from "@hyper-fetch/core";
import { useState, useEffect, useCallback } from "react";
import { UseQueueOptionsType, useQueueDefaultOptions, QueueRequest, UseQueueReturnType } from "hooks/use-queue";
import { useProvider } from "provider";
const canUpdate = (
item: Pick<QueueRequest<RequestInstance>, "success" | "failed" | "canceled" | "removed" | "resolved">,
) => {
if (item.success || item.failed) {
return false;
}
return true;
};
/**
* This hook allows to control dispatchers request queues
* @param request
* @param options
* @returns
*/
export const useQueue = <Request extends RequestInstance>(
request: Request,
options?: UseQueueOptionsType,
): UseQueueReturnType<Request> => {
// Build the configuration options
const { config: globalConfig } = useProvider();
const { dispatcherType, keepFinishedRequests } = {
...useQueueDefaultOptions,
...globalConfig.useQueueConfig,
...options,
};
const { abortKey, queryKey, client } = request;
const { requestManager } = client;
const [dispatcher] = getRequestDispatcher(request, dispatcherType);
const [stopped, setStopped] = useState(false);
const [requests, setRequests] = useState<QueueRequest<Request>[]>([]);
// ******************
// Mapping
// ******************
const createRequestsArray = useCallback(
(queueElements: QueueItemType<Request>[], prevRequests?: QueueRequest<Request>[]): QueueRequest<Request>[] => {
const newRequests = queueElements
// Keep only unique requests
.filter((el) => !prevRequests?.some((prevEl) => prevEl.requestId === el.requestId))
.map<QueueRequest<Request>>((req) => ({
failed: false,
canceled: false,
removed: false,
success: false,
...req,
downloading: {
progress: 0,
timeLeft: 0,
sizeLeft: 0,
total: 0,
loaded: 0,
startTimestamp: 0,
},
uploading: {
progress: 0,
timeLeft: 0,
sizeLeft: 0,
total: 0,
loaded: 0,
startTimestamp: 0,
},
stopRequest: () => dispatcher.stopRequest(queryKey, req.requestId),
startRequest: () => dispatcher.startRequest(queryKey, req.requestId),
deleteRequest: () => dispatcher.delete(queryKey, req.requestId, abortKey),
}));
if (keepFinishedRequests && prevRequests) {
return [...prevRequests, ...newRequests];
}
return newRequests;
},
[abortKey, dispatcher, queryKey, keepFinishedRequests],
);
const mergePayloadType = useCallback((requestId: string, data: Partial<QueueRequest<Request>>) => {
setRequests((prev) =>
prev.map((el) => {
if (el.requestId === requestId && canUpdate(el)) {
return { ...el, ...data };
}
return el;
}),
);
}, []);
// ******************
// State
// ******************
const getInitialState = () => {
const requestQueue = dispatcher.getQueue<Request>(queryKey);
setStopped(requestQueue.stopped);
setRequests(createRequestsArray(requestQueue.requests));
};
const updateQueueState = useCallback(
(values: QueueDataType<Request>) => {
setStopped(values.stopped);
setRequests((prev) => createRequestsArray(values.requests, prev));
},
[createRequestsArray],
);
// ******************
// Events
// ******************
const mountEvents = () => {
const unmountChange = dispatcher.events.onQueueChangeByKey<Request>(queryKey, updateQueueState);
const unmountStatus = dispatcher.events.onQueueStatusChangeByKey<Request>(queryKey, updateQueueState);
const unmountFailed = requestManager.events.onResponse(({ requestId, response }) => {
if (!response.success) {
setRequests((prev) => prev.map((el) => (el.requestId === requestId ? { ...el, failed: true } : el)));
} else {
setRequests((prev) => prev.map((el) => (el.requestId === requestId ? { ...el, success: true } : el)));
}
});
const unmountCanceled = requestManager.events.onAbort(({ requestId }) => {
setRequests((prev) => prev.map((el) => (el.requestId === requestId ? { ...el, canceled: true } : el)));
});
const unmountRemoved = requestManager.events.onRemove(({ requestId }) => {
setRequests((prev) =>
prev.map((el) => (el.requestId === requestId && canUpdate(el) ? { ...el, removed: true } : el)),
);
});
const unmountDownload = requestManager.events.onDownloadProgress(
({ progress, timeLeft, sizeLeft, total, loaded, startTimestamp, requestId }) => {
mergePayloadType(requestId, { downloading: { progress, timeLeft, sizeLeft, total, loaded, startTimestamp } });
},
);
const unmountUpload = requestManager.events.onUploadProgress(
({ progress, timeLeft, sizeLeft, total, loaded, startTimestamp, requestId }) => {
mergePayloadType(requestId, { uploading: { progress, timeLeft, sizeLeft, total, loaded, startTimestamp } });
},
);
const unmount = () => {
unmountStatus();
unmountChange();
unmountDownload();
unmountUpload();
unmountFailed();
unmountCanceled();
unmountRemoved();
};
return unmount;
};
// ******************
// Lifecycle
// ******************
useEffect(getInitialState, [createRequestsArray, dispatcher, queryKey]);
useEffect(mountEvents, [
stopped,
requests,
setRequests,
setStopped,
queryKey,
dispatcher.events,
requestManager.events,
updateQueueState,
mergePayloadType,
]);
return {
stopped,
requests,
dispatcher,
stop: () => dispatcher.stop(queryKey),
pause: () => dispatcher.pause(queryKey),
start: () => dispatcher.start(queryKey),
};
};