From 163822a49f43e4ae5dfffd60c3930030375c2e7c Mon Sep 17 00:00:00 2001 From: Dmitriy Pleshevskiy Date: Wed, 23 Jun 2021 02:30:19 +0300 Subject: [PATCH] feat: add clear request store --- src/lazy_request_hook.ts | 14 ++++++++------ src/reducer.ts | 19 +++++++++++++++++++ target/client.d.ts | 14 +++++++------- target/client.js | 4 ++-- target/endpoint.d.ts | 2 ++ target/endpoint.js | 6 ++++++ target/lazy_request_hook.d.ts | 29 +++++++++++++++-------------- target/lazy_request_hook.js | 22 +++++++++++++--------- target/reducer.d.ts | 29 ++++++++++++++++------------- target/reducer.js | 16 +++++++++++++++- target/request_context.d.ts | 13 +++++-------- target/request_hook.d.ts | 6 +++--- target/request_hook.js | 8 +++----- 13 files changed, 114 insertions(+), 68 deletions(-) diff --git a/src/lazy_request_hook.ts b/src/lazy_request_hook.ts index e334f56..a094bbf 100644 --- a/src/lazy_request_hook.ts +++ b/src/lazy_request_hook.ts @@ -3,7 +3,7 @@ import invariant from 'tiny-invariant'; import isEqual from 'lodash.isequal'; import { useClient } from './client_hook'; import { AnyEndpoint, ExtractEndpointParams, ExtractEndpointResponse, ExtractEndpointVariables, methodWithoutEffects } from './endpoint'; -import { PublicRequestState, RequestReducer, requestReducer } from './reducer'; +import { INITIAL_REQUEST_STATE, PublicRequestState, RequestReducer, requestReducer } from './reducer'; import { useRequestContext } from './request_context'; import { ClientResponse } from './client'; import { isFunction } from './misc'; @@ -38,6 +38,7 @@ extends { readonly refetch: () => void, readonly cancel: () => void, + readonly clearStore: () => void, }; export function useLazyRequest( @@ -48,11 +49,7 @@ export function useLazyRequest( const { defaultHeaders } = useRequestContext(); const [state, dispatch] = React.useReducer>>( requestReducer, - { - data: null, - loading: false, - isCalled: false, - } + INITIAL_REQUEST_STATE, ); const [prevHandlerConfig, setPrevHandlerConfig] = React.useState | null>(null); @@ -165,6 +162,10 @@ export function useLazyRequest( abortControllerRef.current = new AbortController(); }, []); + const clearRequestStore = React.useCallback(() => { + dispatch({ type: 'clearStore' }); + }, []); + React.useEffect( () => cancelRequest, [cancelRequest] @@ -180,6 +181,7 @@ export function useLazyRequest( fetchError: state.fetchError, refetch: refetchRequest, cancel: cancelRequest, + clearStore: clearRequestStore }, ]; } diff --git a/src/reducer.ts b/src/reducer.ts index 98ffe6d..67380ef 100644 --- a/src/reducer.ts +++ b/src/reducer.ts @@ -33,9 +33,24 @@ export type RequestAction = | { type: 'cancel' } + | { + type: 'clearStore' + } export type RequestReducer = React.Reducer, RequestAction> +export const INITIAL_REQUEST_STATE: RequestState = { + data: null, + response: undefined, + fetchError: undefined, + isCanceled: false, + loading: false, + isCalled: false, + prevHeaders: undefined, + prevVariables: undefined, + prevParams: undefined, +}; + export function requestReducer(state: RequestState, action: RequestAction): RequestState { switch (action.type) { case 'call': { @@ -76,5 +91,9 @@ export function requestReducer(state: RequestState, action: RequestAction< fetchError: undefined, }; } + case 'clearStore': { + return INITIAL_REQUEST_STATE; + } } } + diff --git a/target/client.d.ts b/target/client.d.ts index da10bd2..de0581d 100644 --- a/target/client.d.ts +++ b/target/client.d.ts @@ -12,13 +12,13 @@ export interface RequestProps extends PrepareRequestProps { readonly transformResponseData?: (data: unknown) => R; readonly abortSignal: AbortSignal; } -export declare type ResponseWithError = Pick & Readonly<{ - error?: Error; - canceled?: boolean; -}>; -export declare type ClientResponse> = ResponseWithError & Readonly<{ - data: Data; -}>; +export interface ResponseWithError extends Pick { + readonly error?: Error; + readonly canceled?: boolean; +} +export interface ClientResponse> extends ResponseWithError { + readonly data: Data; +} export declare class Client { private readonly config; constructor(config: ClientConfig); diff --git a/target/client.js b/target/client.js index 1a7e251..bbdd641 100644 --- a/target/client.js +++ b/target/client.js @@ -10,7 +10,7 @@ var __rest = (this && this.__rest) || function (s, e) { return t; }; import invariant from 'tiny-invariant'; -import { Method } from './endpoint'; +import { methodCanContainBody } from './endpoint'; import { formDataFromObject, isFunction, urlSearchParamsFromObject } from './misc'; export class Client { constructor(config) { @@ -18,7 +18,7 @@ export class Client { } prepareRequest(props) { var _a; - const requestCanContainBody = [Method.POST, Method.PATCH, Method.PUT].includes(props.method); + const requestCanContainBody = methodCanContainBody(props.method); const defaultBaseUrl = (_a = window) === null || _a === void 0 ? void 0 : _a.location.href; const sourceUrl = /https?:\/\//.test(props.url) ? props.url diff --git a/target/endpoint.d.ts b/target/endpoint.d.ts index 54e2b09..870ec42 100644 --- a/target/endpoint.d.ts +++ b/target/endpoint.d.ts @@ -6,6 +6,8 @@ export declare enum Method { PATCH = "PATCH", DELETE = "DELETE" } +export declare function methodCanContainBody(method: Method): boolean; +export declare function methodWithoutEffects(method: Method): boolean; export declare type Endpoint = Readonly<{ _?: V; method: Method; diff --git a/target/endpoint.js b/target/endpoint.js index a9ca1ca..7a8b1c5 100644 --- a/target/endpoint.js +++ b/target/endpoint.js @@ -7,3 +7,9 @@ export var Method; Method["PATCH"] = "PATCH"; Method["DELETE"] = "DELETE"; })(Method || (Method = {})); +export function methodCanContainBody(method) { + return [Method.POST, Method.PATCH, Method.PUT].includes(method); +} +export function methodWithoutEffects(method) { + return [Method.HEAD, Method.GET].includes(method); +} diff --git a/target/lazy_request_hook.d.ts b/target/lazy_request_hook.d.ts index 4d1fb1f..70aaa92 100644 --- a/target/lazy_request_hook.d.ts +++ b/target/lazy_request_hook.d.ts @@ -1,20 +1,21 @@ import { AnyEndpoint, ExtractEndpointParams, ExtractEndpointResponse, ExtractEndpointVariables } from './endpoint'; import { PublicRequestState } from './reducer'; import { ClientResponse } from './client'; -export declare 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 declare type LazyRequestConfigFromEndpoint = LazyRequestConfig, ExtractEndpointVariables, ExtractEndpointParams>; -export declare type LazyRequestHandlerConfig = Readonly & { - force?: boolean; -}>; +export interface LazyRequestHandlerConfig extends LazyRequestConfigFromEndpoint { + readonly force?: boolean; +} export declare type RequestHandler = (config?: LazyRequestHandlerConfig) => Promise | null>; -export declare type PublicRequestStateWithActions = PublicRequestState> & { - refetch: () => void; - cancel: () => void; -}; +export interface PublicRequestStateWithActions extends PublicRequestState> { + readonly refetch: () => void; + readonly cancel: () => void; + readonly clearStore: () => void; +} export declare function useLazyRequest(endpoint: E, config?: LazyRequestConfigFromEndpoint): [RequestHandler, PublicRequestStateWithActions]; diff --git a/target/lazy_request_hook.js b/target/lazy_request_hook.js index 42a7bfb..e501391 100644 --- a/target/lazy_request_hook.js +++ b/target/lazy_request_hook.js @@ -2,17 +2,15 @@ import React from 'react'; import invariant from 'tiny-invariant'; import isEqual from 'lodash.isequal'; import { useClient } from './client_hook'; -import { requestReducer } from './reducer'; +import { methodWithoutEffects } from './endpoint'; +import { INITIAL_REQUEST_STATE, requestReducer } from './reducer'; import { useRequestContext } from './request_context'; import { isFunction } from './misc'; +; export function useLazyRequest(endpoint, config) { const [client] = useClient(); const { defaultHeaders } = useRequestContext(); - const [state, dispatch] = React.useReducer(requestReducer, { - data: null, - loading: false, - isCalled: false, - }); + const [state, dispatch] = React.useReducer(requestReducer, INITIAL_REQUEST_STATE); const [prevHandlerConfig, setPrevHandlerConfig] = React.useState(null); const abortControllerRef = React.useRef(new AbortController()); const transformResponseData = React.useCallback((data) => { @@ -39,11 +37,13 @@ export function useLazyRequest(endpoint, config) { } const variables = Object.assign(Object.assign({}, config === null || config === void 0 ? void 0 : config.variables), handlerConfig === null || handlerConfig === void 0 ? void 0 : handlerConfig.variables); const headers = Object.assign(Object.assign(Object.assign(Object.assign({}, defaultHeaders), endpoint.headers), config === null || config === void 0 ? void 0 : config.headers), handlerConfig === null || handlerConfig === void 0 ? void 0 : handlerConfig.headers); - if (state.isCalled + const shouldReturnCachedValue = (methodWithoutEffects(endpoint.method) + && state.isCalled && isSameRequest && (state === null || state === void 0 ? void 0 : state.prevVariables) && isEqual(state.prevVariables, variables) && (state === null || state === void 0 ? void 0 : state.prevHeaders) && isEqual(state.prevHeaders, headers) - && (handlerConfig === null || handlerConfig === void 0 ? void 0 : handlerConfig.force) === false) { + && !(handlerConfig === null || handlerConfig === void 0 ? void 0 : handlerConfig.force)); + if (shouldReturnCachedValue) { return Promise.resolve(state.data); } const onCompletes = [config === null || config === void 0 ? void 0 : config.onComplete, handlerConfig === null || handlerConfig === void 0 ? void 0 : handlerConfig.onComplete].filter(isFunction); @@ -68,7 +68,7 @@ export function useLazyRequest(endpoint, config) { }, [state, config, client, endpoint, defaultHeaders, transformResponseData]); const refetchRequest = React.useCallback(() => { if (prevHandlerConfig != null) { - handler(Object.assign(Object.assign({}, prevHandlerConfig), { force: true })); + handler(Object.assign({}, prevHandlerConfig)); } }, [handler, prevHandlerConfig]); const cancelRequest = React.useCallback(() => { @@ -76,6 +76,9 @@ export function useLazyRequest(endpoint, config) { abortControllerRef.current.abort(); abortControllerRef.current = new AbortController(); }, []); + const clearRequestStore = React.useCallback(() => { + dispatch({ type: 'clearStore' }); + }, []); React.useEffect(() => cancelRequest, [cancelRequest]); return [ handler, @@ -87,6 +90,7 @@ export function useLazyRequest(endpoint, config) { fetchError: state.fetchError, refetch: refetchRequest, cancel: cancelRequest, + clearStore: clearRequestStore }, ]; } diff --git a/target/reducer.d.ts b/target/reducer.d.ts index ee44c80..a5e9fa0 100644 --- a/target/reducer.d.ts +++ b/target/reducer.d.ts @@ -1,18 +1,18 @@ /// import { ClientResponse } from './client'; -export declare type PublicRequestState = Readonly<{ - data: R | null; - loading: boolean; - isCalled: boolean; - isCanceled?: boolean; - response?: ClientResponse; - fetchError?: Error; -}>; -export declare type RequestState = PublicRequestState & Readonly<{ - prevHeaders?: Record; - prevVariables?: Record; - prevParams?: Record; -}>; +export interface PublicRequestState { + readonly data: R | null; + readonly loading: boolean; + readonly isCalled: boolean; + readonly isCanceled?: boolean; + readonly response?: ClientResponse; + readonly fetchError?: Error; +} +export interface RequestState extends PublicRequestState { + readonly prevHeaders?: Record; + readonly prevVariables?: Record; + readonly prevParams?: Record; +} export declare type RequestAction = { type: 'call'; headers: Record; @@ -26,6 +26,9 @@ export declare type RequestAction = { response: ClientResponse; } | { type: 'cancel'; +} | { + type: 'clearStore'; }; export declare type RequestReducer = React.Reducer, RequestAction>; +export declare const INITIAL_REQUEST_STATE: RequestState; export declare function requestReducer(state: RequestState, action: RequestAction): RequestState; diff --git a/target/reducer.js b/target/reducer.js index 778d2c0..62bf181 100644 --- a/target/reducer.js +++ b/target/reducer.js @@ -1,3 +1,14 @@ +export const INITIAL_REQUEST_STATE = { + data: null, + response: undefined, + fetchError: undefined, + isCanceled: false, + loading: false, + isCalled: false, + prevHeaders: undefined, + prevVariables: undefined, + prevParams: undefined, +}; export function requestReducer(state, action) { switch (action.type) { case 'call': { @@ -10,7 +21,10 @@ export function requestReducer(state, action) { return Object.assign(Object.assign({}, state), { loading: false, response: action.response, data: null, fetchError: action.response.error, isCanceled: action.response.canceled }); } case 'cancel': { - return Object.assign(Object.assign({}, state), { isCanceled: false, fetchError: undefined }); + return Object.assign(Object.assign({}, state), { isCanceled: true, fetchError: undefined }); + } + case 'clearStore': { + return INITIAL_REQUEST_STATE; } } } diff --git a/target/request_context.d.ts b/target/request_context.d.ts index 41f0e8f..d69c6d6 100644 --- a/target/request_context.d.ts +++ b/target/request_context.d.ts @@ -1,12 +1,9 @@ import React from 'react'; import { Client } from './client'; -export declare type RequestContextData = Readonly<{ - client: Client; - defaultHeaders?: Record; -}>; +export interface RequestContextData { + readonly client: Client; + readonly defaultHeaders?: Record; +} export declare type RequestProviderProps = Readonly>; export declare function RequestProvider({ client, defaultHeaders, children }: RequestProviderProps): JSX.Element; -export declare function useRequestContext(): Readonly<{ - client: Client; - defaultHeaders?: Record | undefined; -}>; +export declare function useRequestContext(): RequestContextData; diff --git a/target/request_hook.d.ts b/target/request_hook.d.ts index ede27df..51ad53f 100644 --- a/target/request_hook.d.ts +++ b/target/request_hook.d.ts @@ -1,6 +1,6 @@ import { AnyEndpoint } from './endpoint'; import { LazyRequestConfigFromEndpoint } from './lazy_request_hook'; -export declare type RequestConfigFromEndpoint = Readonly & { - skip?: boolean; -}>; +export interface RequestConfigFromEndpoint extends LazyRequestConfigFromEndpoint { + readonly skip?: boolean; +} export declare function useRequest(endpoint: E, config?: RequestConfigFromEndpoint): import("./lazy_request_hook").PublicRequestStateWithActions; diff --git a/target/request_hook.js b/target/request_hook.js index bc5b8c1..1e0ff7c 100644 --- a/target/request_hook.js +++ b/target/request_hook.js @@ -1,16 +1,14 @@ import React from 'react'; import invariant from 'tiny-invariant'; -import { Method } from './endpoint'; +import { methodWithoutEffects } from './endpoint'; import { useLazyRequest } from './lazy_request_hook'; export function useRequest(endpoint, config) { - invariant(endpoint.method !== Method.DELETE, `You cannot use useRequest with ${endpoint.method} method`); + invariant(methodWithoutEffects(endpoint.method), `You cannot use useRequest with ${endpoint.method} method`); const [handler, state] = useLazyRequest(endpoint, config); const skip = React.useMemo(() => { var _a; return (_a = config === null || config === void 0 ? void 0 : config.skip) !== null && _a !== void 0 ? _a : false; }, [config]); React.useEffect(() => { if (!skip) { - handler({ - force: false, - }); + handler(); } }, [skip, handler]); return state;