refac: move abort controller to lazy request hook
This commit is contained in:
parent
807493a5e3
commit
f109d87b41
5 changed files with 47 additions and 53 deletions
|
@ -3,18 +3,19 @@ import { Method } from './endpoint';
|
||||||
import { formDataFromObject, isFunction, urlSearchParamsFromObject } from './misc';
|
import { formDataFromObject, isFunction, urlSearchParamsFromObject } from './misc';
|
||||||
|
|
||||||
export interface ClientConfig {
|
export interface ClientConfig {
|
||||||
baseUrl: string,
|
readonly baseUrl: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PrepareRequestProps {
|
export interface PrepareRequestProps {
|
||||||
url: string,
|
readonly url: string,
|
||||||
method: Method,
|
readonly method: Method,
|
||||||
headers: Record<string, string>,
|
readonly headers: Record<string, string>,
|
||||||
variables: Record<string, any> | FormData,
|
readonly variables: Record<string, any> | FormData,
|
||||||
}
|
}
|
||||||
|
|
||||||
export type RequestProps<R> = PrepareRequestProps & {
|
export interface RequestProps<R> extends PrepareRequestProps {
|
||||||
transformResponseData?: (data: unknown) => R,
|
readonly transformResponseData?: (data: unknown) => R,
|
||||||
|
readonly abortSignal: AbortSignal,
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ResponseWithError =
|
export type ResponseWithError =
|
||||||
|
@ -26,8 +27,6 @@ export type ClientResponse<Data extends Record<string, any>> =
|
||||||
& Readonly<{ data: Data }>
|
& Readonly<{ data: Data }>
|
||||||
|
|
||||||
export class Client {
|
export class Client {
|
||||||
private controller = new AbortController();
|
|
||||||
|
|
||||||
constructor(private readonly config: ClientConfig) {}
|
constructor(private readonly config: ClientConfig) {}
|
||||||
|
|
||||||
prepareRequest(props: PrepareRequestProps) {
|
prepareRequest(props: PrepareRequestProps) {
|
||||||
|
@ -82,12 +81,13 @@ export class Client {
|
||||||
request<Data extends Record<string, any>>(
|
request<Data extends Record<string, any>>(
|
||||||
{
|
{
|
||||||
transformResponseData,
|
transformResponseData,
|
||||||
|
abortSignal,
|
||||||
...restProps
|
...restProps
|
||||||
}: RequestProps<Data>
|
}: RequestProps<Data>
|
||||||
): Promise<ClientResponse<Data>> {
|
): Promise<ClientResponse<Data>> {
|
||||||
const req = this.prepareRequest(restProps);
|
const req = this.prepareRequest(restProps);
|
||||||
|
|
||||||
return fetch(req, { signal: this.controller.signal })
|
return fetch(req, { signal: abortSignal })
|
||||||
// TODO: need to check response headers and parse json only if content-type header is application/json
|
// TODO: need to check response headers and parse json only if content-type header is application/json
|
||||||
.then(
|
.then(
|
||||||
(res) => Promise.all([res, res.json()]),
|
(res) => Promise.all([res, res.json()]),
|
||||||
|
@ -131,9 +131,4 @@ export class Client {
|
||||||
return res;
|
return res;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
cancelRequest() {
|
|
||||||
this.controller.abort();
|
|
||||||
this.controller = new AbortController();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,6 +53,8 @@ export function useLazyRequest<E extends AnyEndpoint>(
|
||||||
);
|
);
|
||||||
const [prevHandlerConfig, setPrevHandlerConfig] = React.useState<LazyRequestHandlerConfig<E> | null>(null);
|
const [prevHandlerConfig, setPrevHandlerConfig] = React.useState<LazyRequestHandlerConfig<E> | null>(null);
|
||||||
|
|
||||||
|
const abortControllerRef = React.useRef(new AbortController());
|
||||||
|
|
||||||
const transformResponseData = React.useCallback(
|
const transformResponseData = React.useCallback(
|
||||||
(data: unknown): ExtractEndpointResponse<E> => {
|
(data: unknown): ExtractEndpointResponse<E> => {
|
||||||
return isFunction(endpoint.transformResponseData) ?
|
return isFunction(endpoint.transformResponseData) ?
|
||||||
|
@ -114,6 +116,7 @@ export function useLazyRequest<E extends AnyEndpoint>(
|
||||||
return client
|
return client
|
||||||
.request<ExtractEndpointResponse<E>>({
|
.request<ExtractEndpointResponse<E>>({
|
||||||
...endpoint,
|
...endpoint,
|
||||||
|
abortSignal: abortControllerRef.current.signal,
|
||||||
url: endpointUrl,
|
url: endpointUrl,
|
||||||
headers,
|
headers,
|
||||||
variables,
|
variables,
|
||||||
|
@ -141,7 +144,7 @@ export function useLazyRequest<E extends AnyEndpoint>(
|
||||||
[state, config, client, endpoint, defaultHeaders, transformResponseData]
|
[state, config, client, endpoint, defaultHeaders, transformResponseData]
|
||||||
);
|
);
|
||||||
|
|
||||||
const refetch = React.useCallback(
|
const refetchRequest = React.useCallback(
|
||||||
() => {
|
() => {
|
||||||
if (prevHandlerConfig != null) {
|
if (prevHandlerConfig != null) {
|
||||||
handler({
|
handler({
|
||||||
|
@ -153,14 +156,15 @@ export function useLazyRequest<E extends AnyEndpoint>(
|
||||||
[handler, prevHandlerConfig]
|
[handler, prevHandlerConfig]
|
||||||
);
|
);
|
||||||
|
|
||||||
React.useEffect(
|
const cancelRequest = React.useCallback(() => {
|
||||||
() => {
|
|
||||||
return () => {
|
|
||||||
dispatch({ type: 'cancel' });
|
dispatch({ type: 'cancel' });
|
||||||
client.cancelRequest();
|
abortControllerRef.current.abort();
|
||||||
};
|
abortControllerRef.current = new AbortController();
|
||||||
},
|
}, []);
|
||||||
[client]
|
|
||||||
|
React.useEffect(
|
||||||
|
() => cancelRequest,
|
||||||
|
[cancelRequest]
|
||||||
);
|
);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
@ -171,8 +175,8 @@ export function useLazyRequest<E extends AnyEndpoint>(
|
||||||
isCalled: state.isCalled,
|
isCalled: state.isCalled,
|
||||||
isCanceled: state.isCanceled,
|
isCanceled: state.isCanceled,
|
||||||
fetchError: state.fetchError,
|
fetchError: state.fetchError,
|
||||||
refetch,
|
refetch: refetchRequest,
|
||||||
cancel: client.cancelRequest.bind(client),
|
cancel: cancelRequest,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
21
target/client.d.ts
vendored
21
target/client.d.ts
vendored
|
@ -1,16 +1,17 @@
|
||||||
import { Method } from './endpoint';
|
import { Method } from './endpoint';
|
||||||
export interface ClientConfig {
|
export interface ClientConfig {
|
||||||
baseUrl: string;
|
readonly baseUrl: string;
|
||||||
}
|
}
|
||||||
export interface PrepareRequestProps {
|
export interface PrepareRequestProps {
|
||||||
url: string;
|
readonly url: string;
|
||||||
method: Method;
|
readonly method: Method;
|
||||||
headers: Record<string, string>;
|
readonly headers: Record<string, string>;
|
||||||
variables: Record<string, any> | FormData;
|
readonly variables: Record<string, any> | FormData;
|
||||||
|
}
|
||||||
|
export interface RequestProps<R> extends PrepareRequestProps {
|
||||||
|
readonly transformResponseData?: (data: unknown) => R;
|
||||||
|
readonly abortSignal: AbortSignal;
|
||||||
}
|
}
|
||||||
export declare type RequestProps<R> = PrepareRequestProps & {
|
|
||||||
transformResponseData?: (data: unknown) => R;
|
|
||||||
};
|
|
||||||
export declare type ResponseWithError = Pick<Response, 'ok' | 'redirected' | 'status' | 'statusText' | 'type' | 'headers' | 'url'> & Readonly<{
|
export declare type ResponseWithError = Pick<Response, 'ok' | 'redirected' | 'status' | 'statusText' | 'type' | 'headers' | 'url'> & Readonly<{
|
||||||
error?: Error;
|
error?: Error;
|
||||||
canceled?: boolean;
|
canceled?: boolean;
|
||||||
|
@ -20,9 +21,7 @@ export declare type ClientResponse<Data extends Record<string, any>> = ResponseW
|
||||||
}>;
|
}>;
|
||||||
export declare class Client {
|
export declare class Client {
|
||||||
private readonly config;
|
private readonly config;
|
||||||
private controller;
|
|
||||||
constructor(config: ClientConfig);
|
constructor(config: ClientConfig);
|
||||||
prepareRequest(props: PrepareRequestProps): Request;
|
prepareRequest(props: PrepareRequestProps): Request;
|
||||||
request<Data extends Record<string, any>>({ transformResponseData, ...restProps }: RequestProps<Data>): Promise<ClientResponse<Data>>;
|
request<Data extends Record<string, any>>({ transformResponseData, abortSignal, ...restProps }: RequestProps<Data>): Promise<ClientResponse<Data>>;
|
||||||
cancelRequest(): void;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,6 @@ import { formDataFromObject, isFunction, urlSearchParamsFromObject } from './mis
|
||||||
export class Client {
|
export class Client {
|
||||||
constructor(config) {
|
constructor(config) {
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.controller = new AbortController();
|
|
||||||
}
|
}
|
||||||
prepareRequest(props) {
|
prepareRequest(props) {
|
||||||
var _a;
|
var _a;
|
||||||
|
@ -47,9 +46,9 @@ export class Client {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
request(_a) {
|
request(_a) {
|
||||||
var { transformResponseData } = _a, restProps = __rest(_a, ["transformResponseData"]);
|
var { transformResponseData, abortSignal } = _a, restProps = __rest(_a, ["transformResponseData", "abortSignal"]);
|
||||||
const req = this.prepareRequest(restProps);
|
const req = this.prepareRequest(restProps);
|
||||||
return fetch(req, { signal: this.controller.signal })
|
return fetch(req, { signal: abortSignal })
|
||||||
// TODO: need to check response headers and parse json only if content-type header is application/json
|
// TODO: need to check response headers and parse json only if content-type header is application/json
|
||||||
.then((res) => Promise.all([res, res.json()]), (err) => {
|
.then((res) => Promise.all([res, res.json()]), (err) => {
|
||||||
const canceled = err.name === 'AbortError';
|
const canceled = err.name === 'AbortError';
|
||||||
|
@ -89,8 +88,4 @@ export class Client {
|
||||||
return res;
|
return res;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
cancelRequest() {
|
|
||||||
this.controller.abort();
|
|
||||||
this.controller = new AbortController();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ export function useLazyRequest(endpoint, config) {
|
||||||
isCalled: false,
|
isCalled: false,
|
||||||
});
|
});
|
||||||
const [prevHandlerConfig, setPrevHandlerConfig] = React.useState(null);
|
const [prevHandlerConfig, setPrevHandlerConfig] = React.useState(null);
|
||||||
|
const abortControllerRef = React.useRef(new AbortController());
|
||||||
const transformResponseData = React.useCallback((data) => {
|
const transformResponseData = React.useCallback((data) => {
|
||||||
return isFunction(endpoint.transformResponseData) ?
|
return isFunction(endpoint.transformResponseData) ?
|
||||||
endpoint.transformResponseData(data)
|
endpoint.transformResponseData(data)
|
||||||
|
@ -50,7 +51,7 @@ export function useLazyRequest(endpoint, config) {
|
||||||
dispatch({ type: 'call', headers, variables, params });
|
dispatch({ type: 'call', headers, variables, params });
|
||||||
setPrevHandlerConfig(handlerConfig !== null && handlerConfig !== void 0 ? handlerConfig : {});
|
setPrevHandlerConfig(handlerConfig !== null && handlerConfig !== void 0 ? handlerConfig : {});
|
||||||
return client
|
return client
|
||||||
.request(Object.assign(Object.assign({}, endpoint), { url: endpointUrl, headers,
|
.request(Object.assign(Object.assign({}, endpoint), { abortSignal: abortControllerRef.current.signal, url: endpointUrl, headers,
|
||||||
variables,
|
variables,
|
||||||
transformResponseData }))
|
transformResponseData }))
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
|
@ -65,17 +66,17 @@ export function useLazyRequest(endpoint, config) {
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
}, [state, config, client, endpoint, defaultHeaders, transformResponseData]);
|
}, [state, config, client, endpoint, defaultHeaders, transformResponseData]);
|
||||||
const refetch = React.useCallback(() => {
|
const refetchRequest = React.useCallback(() => {
|
||||||
if (prevHandlerConfig != null) {
|
if (prevHandlerConfig != null) {
|
||||||
handler(Object.assign(Object.assign({}, prevHandlerConfig), { force: true }));
|
handler(Object.assign(Object.assign({}, prevHandlerConfig), { force: true }));
|
||||||
}
|
}
|
||||||
}, [handler, prevHandlerConfig]);
|
}, [handler, prevHandlerConfig]);
|
||||||
React.useEffect(() => {
|
const cancelRequest = React.useCallback(() => {
|
||||||
return () => {
|
|
||||||
dispatch({ type: 'cancel' });
|
dispatch({ type: 'cancel' });
|
||||||
client.cancelRequest();
|
abortControllerRef.current.abort();
|
||||||
};
|
abortControllerRef.current = new AbortController();
|
||||||
}, [client]);
|
}, []);
|
||||||
|
React.useEffect(() => cancelRequest, [cancelRequest]);
|
||||||
return [
|
return [
|
||||||
handler,
|
handler,
|
||||||
{
|
{
|
||||||
|
@ -84,8 +85,8 @@ export function useLazyRequest(endpoint, config) {
|
||||||
isCalled: state.isCalled,
|
isCalled: state.isCalled,
|
||||||
isCanceled: state.isCanceled,
|
isCanceled: state.isCanceled,
|
||||||
fetchError: state.fetchError,
|
fetchError: state.fetchError,
|
||||||
refetch,
|
refetch: refetchRequest,
|
||||||
cancel: client.cancelRequest.bind(client),
|
cancel: cancelRequest,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
Reference in a new issue