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:
Dmitriy Pleshevskiy 2021-06-23 02:05:48 +03:00
parent 1a8ed09d85
commit 39d80815c9
6 changed files with 75 additions and 59 deletions

View file

@ -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) ?

View file

@ -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;

View file

@ -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]

View file

@ -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,
};
}
}
}
}

View file

@ -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);

View file

@ -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]