From 39d80815c94b612a65554cb86ded81db7ab797d4 Mon Sep 17 00:00:00 2001 From: Dmitriy Pleshevskiy Date: Wed, 23 Jun 2021 02:05:48 +0300 Subject: [PATCH] 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 --- src/client.ts | 19 ++++++++++----- src/endpoint.ts | 8 +++++++ src/lazy_request_hook.ts | 51 +++++++++++++++++++++------------------- src/reducer.ts | 30 +++++++++++------------ src/request_context.tsx | 8 +++---- src/request_hook.ts | 18 +++++++------- 6 files changed, 75 insertions(+), 59 deletions(-) diff --git a/src/client.ts b/src/client.ts index 5cfbcad..79a04cb 100644 --- a/src/client.ts +++ b/src/client.ts @@ -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 extends PrepareRequestProps { readonly abortSignal: AbortSignal, } -export type ResponseWithError = +export interface ResponseWithError +extends Pick - & Readonly<{ error?: Error, canceled?: boolean }> +{ + readonly error?: Error, + readonly canceled?: boolean, +} -export type ClientResponse> = +export interface ClientResponse> +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) ? diff --git a/src/endpoint.ts b/src/endpoint.ts index 520cf9f..80c73c6 100644 --- a/src/endpoint.ts +++ b/src/endpoint.ts @@ -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 = Readonly<{ _?: V; // Temporary hack to extract the type of variables. Do not use it in real endpoints. method: Method; diff --git a/src/lazy_request_hook.ts b/src/lazy_request_hook.ts index 87acc67..e334f56 100644 --- a/src/lazy_request_hook.ts +++ b/src/lazy_request_hook.ts @@ -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 = Readonly<{ - variables?: V; - params?: P; - headers?: Record; - onComplete?: (data: R) => unknown; - onFailure?: (res: ClientResponse) => unknown; -}> +export interface LazyRequestConfig { + readonly variables?: V; + readonly params?: P; + readonly headers?: Record; + readonly onComplete?: (data: R) => unknown; + readonly onFailure?: (res: ClientResponse) => unknown; +} export type LazyRequestConfigFromEndpoint = LazyRequestConfig< ExtractEndpointResponse, @@ -22,20 +22,23 @@ export type LazyRequestConfigFromEndpoint = LazyRequestCo ExtractEndpointParams >; -export type LazyRequestHandlerConfig = Readonly< +export interface LazyRequestHandlerConfig +extends LazyRequestConfigFromEndpoint - & { force?: boolean } -> +{ + readonly force?: boolean +} export type RequestHandler = (config?: LazyRequestHandlerConfig) => Promise | null>; -export type PublicRequestStateWithActions = +export interface PublicRequestStateWithActions +extends PublicRequestState> - & { - refetch: () => void, - cancel: () => void, - }; +{ + readonly refetch: () => void, + readonly cancel: () => void, +}; export function useLazyRequest( endpoint: E, @@ -96,13 +99,16 @@ export function useLazyRequest( ...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( const refetchRequest = React.useCallback( () => { if (prevHandlerConfig != null) { - handler({ - ...prevHandlerConfig, - force: true, - }); + handler({ ...prevHandlerConfig }); } }, [handler, prevHandlerConfig] diff --git a/src/reducer.ts b/src/reducer.ts index 51ca12c..98ffe6d 100644 --- a/src/reducer.ts +++ b/src/reducer.ts @@ -1,19 +1,19 @@ import { ClientResponse } from './client'; -export type PublicRequestState = Readonly<{ - data: R | null; - loading: boolean; - isCalled: boolean; - isCanceled?: boolean; - response?: ClientResponse; - fetchError?: Error; -}>; +export interface PublicRequestState { + readonly data: R | null; + readonly loading: boolean; + readonly isCalled: boolean; + readonly isCanceled?: boolean; + readonly response?: ClientResponse; + readonly fetchError?: Error; +} -export type RequestState = PublicRequestState & Readonly<{ - prevHeaders?: Record; - prevVariables?: Record; - prevParams?: Record; -}> +export interface RequestState extends PublicRequestState { + readonly prevHeaders?: Record; + readonly prevVariables?: Record; + readonly prevParams?: Record; +} export type RequestAction = | { @@ -72,9 +72,9 @@ export function requestReducer(state: RequestState, action: RequestAction< case 'cancel': { return { ...state, - isCanceled: false, + isCanceled: true, fetchError: undefined, }; } } -} \ No newline at end of file +} diff --git a/src/request_context.tsx b/src/request_context.tsx index bbe84d8..b2d7e70 100644 --- a/src/request_context.tsx +++ b/src/request_context.tsx @@ -3,10 +3,10 @@ import invariant from 'tiny-invariant'; import { Client } from './client'; -export type RequestContextData = Readonly<{ - client: Client; - defaultHeaders?: Record; -}> +export interface RequestContextData { + readonly client: Client; + readonly defaultHeaders?: Record; +} const RequestContext = React.createContext(null); diff --git a/src/request_hook.ts b/src/request_hook.ts index 9cc056f..b1353e8 100644 --- a/src/request_hook.ts +++ b/src/request_hook.ts @@ -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 = Readonly< +export interface RequestConfigFromEndpoint +extends LazyRequestConfigFromEndpoint - & { - skip?: boolean, - } -> +{ + readonly skip?: boolean, +} export function useRequest( endpoint: E, config?: RequestConfigFromEndpoint, ) { invariant( - endpoint.method !== Method.DELETE, + methodWithoutEffects(endpoint.method), `You cannot use useRequest with ${endpoint.method} method` ); @@ -25,9 +25,7 @@ export function useRequest( React.useEffect( () => { if (!skip) { - handler({ - force: false, - }); + handler(); } }, [skip, handler]