refac!: change accessibility for request hook
BREAKING CHANGES: now only endpoints with method without side-effects (HEAD, GET) can be used in `useRequest` hook. style: change type to interface
This commit is contained in:
parent
1a8ed09d85
commit
39d80815c9
6 changed files with 75 additions and 59 deletions
|
@ -1,5 +1,5 @@
|
|||
import invariant from 'tiny-invariant';
|
||||
import { Method } from './endpoint';
|
||||
import { Method, methodCanContainBody } from './endpoint';
|
||||
import { formDataFromObject, isFunction, urlSearchParamsFromObject } from './misc';
|
||||
|
||||
export interface ClientConfig {
|
||||
|
@ -18,19 +18,26 @@ export interface RequestProps<R> extends PrepareRequestProps {
|
|||
readonly abortSignal: AbortSignal,
|
||||
}
|
||||
|
||||
export type ResponseWithError =
|
||||
export interface ResponseWithError
|
||||
extends
|
||||
Pick<Response, 'ok' | 'redirected' | 'status' | 'statusText' | 'type' | 'headers' | 'url'>
|
||||
& Readonly<{ error?: Error, canceled?: boolean }>
|
||||
{
|
||||
readonly error?: Error,
|
||||
readonly canceled?: boolean,
|
||||
}
|
||||
|
||||
export type ClientResponse<Data extends Record<string, any>> =
|
||||
export interface ClientResponse<Data extends Record<string, any>>
|
||||
extends
|
||||
ResponseWithError
|
||||
& Readonly<{ data: Data }>
|
||||
{
|
||||
readonly data: Data
|
||||
}
|
||||
|
||||
export class Client {
|
||||
constructor(private readonly config: ClientConfig) {}
|
||||
|
||||
prepareRequest(props: PrepareRequestProps) {
|
||||
const requestCanContainBody = [Method.POST, Method.PATCH, Method.PUT].includes(props.method);
|
||||
const requestCanContainBody = methodCanContainBody(props.method);
|
||||
|
||||
const defaultBaseUrl = (window as Window | undefined)?.location.href;
|
||||
const sourceUrl = /https?:\/\//.test(props.url) ?
|
||||
|
|
|
@ -8,6 +8,14 @@ export enum Method {
|
|||
DELETE = 'DELETE',
|
||||
}
|
||||
|
||||
export function methodCanContainBody(method: Method) {
|
||||
return [Method.POST, Method.PATCH, Method.PUT].includes(method);
|
||||
}
|
||||
|
||||
export function methodWithoutEffects(method: Method) {
|
||||
return [Method.HEAD, Method.GET].includes(method);
|
||||
}
|
||||
|
||||
export type Endpoint<R, V, P = unknown> = Readonly<{
|
||||
_?: V; // Temporary hack to extract the type of variables. Do not use it in real endpoints.
|
||||
method: Method;
|
||||
|
|
|
@ -2,19 +2,19 @@ import React from 'react';
|
|||
import invariant from 'tiny-invariant';
|
||||
import isEqual from 'lodash.isequal';
|
||||
import { useClient } from './client_hook';
|
||||
import { AnyEndpoint, ExtractEndpointParams, ExtractEndpointResponse, ExtractEndpointVariables } from './endpoint';
|
||||
import { AnyEndpoint, ExtractEndpointParams, ExtractEndpointResponse, ExtractEndpointVariables, methodWithoutEffects } from './endpoint';
|
||||
import { PublicRequestState, RequestReducer, requestReducer } from './reducer';
|
||||
import { useRequestContext } from './request_context';
|
||||
import { ClientResponse } from './client';
|
||||
import { isFunction } from './misc';
|
||||
|
||||
export type LazyRequestConfig<R, V, P> = Readonly<{
|
||||
variables?: V;
|
||||
params?: P;
|
||||
headers?: Record<string, string>;
|
||||
onComplete?: (data: R) => unknown;
|
||||
onFailure?: (res: ClientResponse<R>) => unknown;
|
||||
}>
|
||||
export interface LazyRequestConfig<R, V, P> {
|
||||
readonly variables?: V;
|
||||
readonly params?: P;
|
||||
readonly headers?: Record<string, string>;
|
||||
readonly onComplete?: (data: R) => unknown;
|
||||
readonly onFailure?: (res: ClientResponse<R>) => unknown;
|
||||
}
|
||||
|
||||
export type LazyRequestConfigFromEndpoint<E extends AnyEndpoint> = LazyRequestConfig<
|
||||
ExtractEndpointResponse<E>,
|
||||
|
@ -22,20 +22,23 @@ export type LazyRequestConfigFromEndpoint<E extends AnyEndpoint> = LazyRequestCo
|
|||
ExtractEndpointParams<E>
|
||||
>;
|
||||
|
||||
export type LazyRequestHandlerConfig<E extends AnyEndpoint> = Readonly<
|
||||
export interface LazyRequestHandlerConfig<E extends AnyEndpoint>
|
||||
extends
|
||||
LazyRequestConfigFromEndpoint<E>
|
||||
& { force?: boolean }
|
||||
>
|
||||
{
|
||||
readonly force?: boolean
|
||||
}
|
||||
|
||||
export type RequestHandler<E extends AnyEndpoint> =
|
||||
(config?: LazyRequestHandlerConfig<E>) => Promise<ExtractEndpointResponse<E> | null>;
|
||||
|
||||
export type PublicRequestStateWithActions<E extends AnyEndpoint> =
|
||||
export interface PublicRequestStateWithActions<E extends AnyEndpoint>
|
||||
extends
|
||||
PublicRequestState<ExtractEndpointResponse<E>>
|
||||
& {
|
||||
refetch: () => void,
|
||||
cancel: () => void,
|
||||
};
|
||||
{
|
||||
readonly refetch: () => void,
|
||||
readonly cancel: () => void,
|
||||
};
|
||||
|
||||
export function useLazyRequest<E extends AnyEndpoint>(
|
||||
endpoint: E,
|
||||
|
@ -96,13 +99,16 @@ export function useLazyRequest<E extends AnyEndpoint>(
|
|||
...handlerConfig?.headers,
|
||||
};
|
||||
|
||||
if (
|
||||
state.isCalled
|
||||
const shouldReturnCachedValue = (
|
||||
methodWithoutEffects(endpoint.method)
|
||||
&& state.isCalled
|
||||
&& isSameRequest
|
||||
&& state?.prevVariables && isEqual(state.prevVariables, variables)
|
||||
&& state?.prevHeaders && isEqual(state.prevHeaders, headers)
|
||||
&& handlerConfig?.force === false
|
||||
) {
|
||||
&& !handlerConfig?.force
|
||||
);
|
||||
|
||||
if (shouldReturnCachedValue) {
|
||||
return Promise.resolve(state.data);
|
||||
}
|
||||
|
||||
|
@ -147,10 +153,7 @@ export function useLazyRequest<E extends AnyEndpoint>(
|
|||
const refetchRequest = React.useCallback(
|
||||
() => {
|
||||
if (prevHandlerConfig != null) {
|
||||
handler({
|
||||
...prevHandlerConfig,
|
||||
force: true,
|
||||
});
|
||||
handler({ ...prevHandlerConfig });
|
||||
}
|
||||
},
|
||||
[handler, prevHandlerConfig]
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
import { ClientResponse } from './client';
|
||||
|
||||
export type PublicRequestState<R> = Readonly<{
|
||||
data: R | null;
|
||||
loading: boolean;
|
||||
isCalled: boolean;
|
||||
isCanceled?: boolean;
|
||||
response?: ClientResponse<R>;
|
||||
fetchError?: Error;
|
||||
}>;
|
||||
export interface PublicRequestState<R> {
|
||||
readonly data: R | null;
|
||||
readonly loading: boolean;
|
||||
readonly isCalled: boolean;
|
||||
readonly isCanceled?: boolean;
|
||||
readonly response?: ClientResponse<R>;
|
||||
readonly fetchError?: Error;
|
||||
}
|
||||
|
||||
export type RequestState<R> = PublicRequestState<R> & Readonly<{
|
||||
prevHeaders?: Record<string, string>;
|
||||
prevVariables?: Record<string, any>;
|
||||
prevParams?: Record<string, any>;
|
||||
}>
|
||||
export interface RequestState<R> extends PublicRequestState<R> {
|
||||
readonly prevHeaders?: Record<string, string>;
|
||||
readonly prevVariables?: Record<string, any>;
|
||||
readonly prevParams?: Record<string, any>;
|
||||
}
|
||||
|
||||
export type RequestAction<R> =
|
||||
| {
|
||||
|
@ -72,9 +72,9 @@ export function requestReducer<R>(state: RequestState<R>, action: RequestAction<
|
|||
case 'cancel': {
|
||||
return {
|
||||
...state,
|
||||
isCanceled: false,
|
||||
isCanceled: true,
|
||||
fetchError: undefined,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,10 +3,10 @@ import invariant from 'tiny-invariant';
|
|||
import { Client } from './client';
|
||||
|
||||
|
||||
export type RequestContextData = Readonly<{
|
||||
client: Client;
|
||||
defaultHeaders?: Record<string, string>;
|
||||
}>
|
||||
export interface RequestContextData {
|
||||
readonly client: Client;
|
||||
readonly defaultHeaders?: Record<string, string>;
|
||||
}
|
||||
|
||||
const RequestContext = React.createContext<RequestContextData | null>(null);
|
||||
|
||||
|
|
|
@ -1,21 +1,21 @@
|
|||
import React from 'react';
|
||||
import invariant from 'tiny-invariant';
|
||||
import { AnyEndpoint, Method } from './endpoint';
|
||||
import { AnyEndpoint, methodWithoutEffects } from './endpoint';
|
||||
import { LazyRequestConfigFromEndpoint, useLazyRequest } from './lazy_request_hook';
|
||||
|
||||
export type RequestConfigFromEndpoint<E extends AnyEndpoint> = Readonly<
|
||||
export interface RequestConfigFromEndpoint<E extends AnyEndpoint>
|
||||
extends
|
||||
LazyRequestConfigFromEndpoint<E>
|
||||
& {
|
||||
skip?: boolean,
|
||||
}
|
||||
>
|
||||
{
|
||||
readonly skip?: boolean,
|
||||
}
|
||||
|
||||
export function useRequest<E extends AnyEndpoint>(
|
||||
endpoint: E,
|
||||
config?: RequestConfigFromEndpoint<E>,
|
||||
) {
|
||||
invariant(
|
||||
endpoint.method !== Method.DELETE,
|
||||
methodWithoutEffects(endpoint.method),
|
||||
`You cannot use useRequest with ${endpoint.method} method`
|
||||
);
|
||||
|
||||
|
@ -25,9 +25,7 @@ export function useRequest<E extends AnyEndpoint>(
|
|||
React.useEffect(
|
||||
() => {
|
||||
if (!skip) {
|
||||
handler({
|
||||
force: false,
|
||||
});
|
||||
handler();
|
||||
}
|
||||
},
|
||||
[skip, handler]
|
||||
|
|
Reference in a new issue