From 2e1d2610bb26876e493d22655c4abd30dc51f511 Mon Sep 17 00:00:00 2001 From: Luis Alvarez <luis@vercel.com> Date: Thu, 18 Feb 2021 23:22:45 -0500 Subject: [PATCH] Moved more hooks and updated types to make them smaller --- framework/bigcommerce/cart/use-cart.tsx | 11 ++- .../bigcommerce/cart/use-remove-item.tsx | 2 +- framework/commerce/cart/use-add-item.tsx | 11 ++- framework/commerce/cart/use-cart.tsx | 64 +++++----------- framework/commerce/cart/use-remove-item.tsx | 13 ++-- framework/commerce/cart/use-update-item.tsx | 13 ++-- framework/commerce/index.tsx | 4 +- framework/commerce/utils/default-fetcher.ts | 2 +- framework/commerce/utils/types.ts | 38 +++++++++- framework/commerce/utils/use-data.tsx | 9 ++- framework/commerce/utils/use-hook.ts | 73 ++++++++++--------- 11 files changed, 128 insertions(+), 112 deletions(-) diff --git a/framework/bigcommerce/cart/use-cart.tsx b/framework/bigcommerce/cart/use-cart.tsx index b5cc0cccf..2098e7431 100644 --- a/framework/bigcommerce/cart/use-cart.tsx +++ b/framework/bigcommerce/cart/use-cart.tsx @@ -1,13 +1,12 @@ import { useMemo } from 'react' -import { HookHandler } from '@commerce/utils/types' +import { SWRHook } from '@commerce/utils/types' import useCart, { UseCart, FetchCartInput } from '@commerce/cart/use-cart' import { normalizeCart } from '../lib/normalize' import type { Cart } from '../types' -import type { BigcommerceProvider } from '..' -export default useCart as UseCart<BigcommerceProvider> +export default useCart as UseCart<typeof handler> -export const handler: HookHandler< +export const handler: SWRHook< Cart | null, {}, FetchCartInput, @@ -21,9 +20,9 @@ export const handler: HookHandler< const data = cartId ? await fetch(options) : null return data && normalizeCart(data) }, - useHook({ input, useData }) { + useHook: ({ useData }) => (input) => { const response = useData({ - swrOptions: { revalidateOnFocus: false, ...input.swrOptions }, + swrOptions: { revalidateOnFocus: false, ...input?.swrOptions }, }) return useMemo( diff --git a/framework/bigcommerce/cart/use-remove-item.tsx b/framework/bigcommerce/cart/use-remove-item.tsx index c1f1ab4c0..f279b2dc6 100644 --- a/framework/bigcommerce/cart/use-remove-item.tsx +++ b/framework/bigcommerce/cart/use-remove-item.tsx @@ -46,7 +46,7 @@ export const handler = { ctx: { item?: T } = {} ) => { const { item } = ctx - const { mutate } = useCart() as any + const { mutate } = useCart() const removeItem: RemoveItemFn<LineItem> = async (input) => { const itemId = input?.id ?? item?.id diff --git a/framework/commerce/cart/use-add-item.tsx b/framework/commerce/cart/use-add-item.tsx index 715029d18..13da6b416 100644 --- a/framework/commerce/cart/use-add-item.tsx +++ b/framework/commerce/cart/use-add-item.tsx @@ -1,4 +1,5 @@ -import useHook, { useHookHandler } from '../utils/use-hook' +import { useHook, useMutationHook } from '../utils/use-hook' +import { mutationFetcher } from '../utils/default-fetcher' import type { MutationHook, HookFetcherFn } from '../utils/types' import type { Cart, CartItemBody, AddCartItemBody } from '../types' import type { Provider } from '..' @@ -6,9 +7,7 @@ import type { Provider } from '..' export const fetcher: HookFetcherFn< Cart, AddCartItemBody<CartItemBody> -> = async ({ options, input, fetch }) => { - return fetch({ ...options, body: input }) -} +> = mutationFetcher export type UseAddItem< H extends MutationHook<any, any, any> = MutationHook<Cart, {}, CartItemBody> @@ -17,8 +16,8 @@ export type UseAddItem< const fn = (provider: Provider) => provider.cart?.useAddItem! const useAddItem: UseAddItem = (...args) => { - const handler = useHookHandler(fn, fetcher) - return handler(useHook(fn, fetcher))(...args) + const hook = useHook(fn) + return useMutationHook({ fetcher, ...hook })(...args) } export default useAddItem diff --git a/framework/commerce/cart/use-cart.tsx b/framework/commerce/cart/use-cart.tsx index f7b384047..6053c91d7 100644 --- a/framework/commerce/cart/use-cart.tsx +++ b/framework/commerce/cart/use-cart.tsx @@ -1,34 +1,21 @@ import Cookies from 'js-cookie' import type { Cart } from '../types' -import type { - Prop, - HookFetcherFn, - UseHookInput, - UseHookResponse, -} from '../utils/types' -import useData from '../utils/use-data' +import type { HookFetcherFn, SWRHook } from '../utils/types' import { Provider, useCommerce } from '..' +import { useHook, useSWRHook } from '@commerce/utils/use-hook' export type FetchCartInput = { cartId?: Cart['id'] } -export type UseCartHandler<P extends Provider> = Prop< - Prop<P, 'cart'>, - 'useCart' -> - -export type UseCartInput<P extends Provider> = UseHookInput<UseCartHandler<P>> - -export type CartResponse<P extends Provider> = UseHookResponse< - UseCartHandler<P> -> - -export type UseCart<P extends Provider> = Partial< - UseCartInput<P> -> extends UseCartInput<P> - ? (input?: UseCartInput<P>) => CartResponse<P> - : (input: UseCartInput<P>) => CartResponse<P> +export type UseCart< + H extends SWRHook<any, any, any> = SWRHook< + Cart | null, + {}, + FetchCartInput, + { isEmpty?: boolean } + > +> = ReturnType<H['useHook']> export const fetcher: HookFetcherFn<Cart | null, FetchCartInput> = async ({ options, @@ -38,32 +25,17 @@ export const fetcher: HookFetcherFn<Cart | null, FetchCartInput> = async ({ return cartId ? await fetch({ ...options }) : null } -export default function useCart<P extends Provider>( - input: UseCartInput<P> = {} -) { - const { providerRef, fetcherRef, cartCookie } = useCommerce<P>() - - const provider = providerRef.current - const opts = provider.cart?.useCart - - const fetcherFn = opts?.fetcher ?? fetcher - const useHook = opts?.useHook ?? ((ctx) => ctx.useData()) +const fn = (provider: Provider) => provider.cart?.useCart! +const useCart: UseCart = (input) => { + const hook = useHook(fn) + const { cartCookie } = useCommerce() + const fetcherFn = hook.fetcher ?? fetcher const wrapper: typeof fetcher = (context) => { context.input.cartId = Cookies.get(cartCookie) return fetcherFn(context) } - - return useHook({ - input, - useData(ctx) { - const response = useData( - { ...opts!, fetcher: wrapper }, - ctx?.input ?? [], - provider.fetcher ?? fetcherRef.current, - ctx?.swrOptions ?? input.swrOptions - ) - return response - }, - }) + return useSWRHook({ ...hook, fetcher: wrapper })(input) } + +export default useCart diff --git a/framework/commerce/cart/use-remove-item.tsx b/framework/commerce/cart/use-remove-item.tsx index 32d8cbf1c..a9d1b37d2 100644 --- a/framework/commerce/cart/use-remove-item.tsx +++ b/framework/commerce/cart/use-remove-item.tsx @@ -1,6 +1,6 @@ -import useHook, { useHookHandler } from '../utils/use-hook' +import { useHook, useMutationHook } from '../utils/use-hook' import { mutationFetcher } from '../utils/default-fetcher' -import type { MutationHook } from '../utils/types' +import type { HookFetcherFn, MutationHook } from '../utils/types' import type { Cart, LineItem, RemoveCartItemBody } from '../types' import type { Provider } from '..' @@ -20,13 +20,16 @@ export type UseRemoveItem< > > = ReturnType<H['useHook']> -export const fetcher = mutationFetcher +export const fetcher: HookFetcherFn< + Cart | null, + RemoveCartItemBody +> = mutationFetcher const fn = (provider: Provider) => provider.cart?.useRemoveItem! const useRemoveItem: UseRemoveItem = (input) => { - const handler = useHookHandler(fn, fetcher) - return handler(useHook(fn, fetcher))(input) + const hook = useHook(fn) + return useMutationHook({ fetcher, ...hook })(input) } export default useRemoveItem diff --git a/framework/commerce/cart/use-update-item.tsx b/framework/commerce/cart/use-update-item.tsx index 93afdbb1e..f8d0f1a40 100644 --- a/framework/commerce/cart/use-update-item.tsx +++ b/framework/commerce/cart/use-update-item.tsx @@ -1,6 +1,6 @@ -import useHook, { useHookHandler } from '../utils/use-hook' +import { useHook, useMutationHook } from '../utils/use-hook' import { mutationFetcher } from '../utils/default-fetcher' -import type { MutationHook } from '../utils/types' +import type { HookFetcherFn, MutationHook } from '../utils/types' import type { Cart, CartItemBody, LineItem, UpdateCartItemBody } from '../types' import type { Provider } from '..' @@ -23,13 +23,16 @@ export type UseUpdateItem< > > = ReturnType<H['useHook']> -export const fetcher = mutationFetcher +export const fetcher: HookFetcherFn< + Cart | null, + UpdateCartItemBody<CartItemBody> +> = mutationFetcher const fn = (provider: Provider) => provider.cart?.useUpdateItem! const useUpdateItem: UseUpdateItem = (input) => { - const handler = useHookHandler(fn, fetcher) - return handler(useHook(fn, fetcher))(input) + const hook = useHook(fn) + return useMutationHook({ fetcher, ...hook })(input) } export default useUpdateItem diff --git a/framework/commerce/index.tsx b/framework/commerce/index.tsx index 1a2ba878b..6ce2c8176 100644 --- a/framework/commerce/index.tsx +++ b/framework/commerce/index.tsx @@ -6,7 +6,7 @@ import { useMemo, useRef, } from 'react' -import { Fetcher, HookHandler, MutationHook } from './utils/types' +import { Fetcher, HookHandler, SWRHook, MutationHook } from './utils/types' import type { FetchCartInput } from './cart/use-cart' import type { Cart, Wishlist, Customer, SearchProductsData } from './types' @@ -15,7 +15,7 @@ const Commerce = createContext<CommerceContextValue<any> | {}>({}) export type Provider = CommerceConfig & { fetcher: Fetcher cart?: { - useCart?: HookHandler<Cart | null, any, FetchCartInput> + useCart?: SWRHook<Cart | null, any, FetchCartInput> useAddItem?: MutationHook<any, any, any> useUpdateItem?: MutationHook<any, any, any> useRemoveItem?: MutationHook<any, any, any> diff --git a/framework/commerce/utils/default-fetcher.ts b/framework/commerce/utils/default-fetcher.ts index cdaf05516..654da2499 100644 --- a/framework/commerce/utils/default-fetcher.ts +++ b/framework/commerce/utils/default-fetcher.ts @@ -3,7 +3,7 @@ import type { HookFetcherFn } from './types' const defaultFetcher: HookFetcherFn<any> = ({ options, fetch }) => fetch(options) -export const mutationFetcher: HookFetcherFn<any> = ({ +export const mutationFetcher: HookFetcherFn<any, any> = ({ input, options, fetch, diff --git a/framework/commerce/utils/types.ts b/framework/commerce/utils/types.ts index 98e4d0f34..01f1c2eb3 100644 --- a/framework/commerce/utils/types.ts +++ b/framework/commerce/utils/types.ts @@ -9,7 +9,9 @@ export type Override<T, K> = Omit<T, keyof K> & K * Returns the properties in T with the properties in type K changed from optional to required */ export type PickRequired<T, K extends keyof T> = Omit<T, K> & - Required<Pick<T, K>> + { + [P in K]-?: NonNullable<T[P]> + } /** * Core fetcher added by CommerceProvider @@ -83,6 +85,36 @@ export type HookFunction< ? (input?: Input) => T : (input: Input) => T +export type SWRHook< + // Data obj returned by the hook and fetch operation + Data, + // Input expected by the hook + Input extends { [k: string]: unknown } = {}, + // Input expected before doing a fetch operation + FetchInput extends HookFetchInput = {}, + // Custom state added to the response object of SWR + State = {} +> = { + useHook( + context: SWRHookContext<Data, FetchInput> + ): HookFunction< + Input & { swrOptions?: SwrOptions<Data, FetchInput> }, + ResponseState<Data> & State + > + fetchOptions: HookFetcherOptions + fetcher?: HookFetcherFn<Data, FetchInput> +} + +export type SWRHookContext< + Data, + FetchInput extends { [k: string]: unknown } = {} +> = { + useData(context?: { + input?: HookFetchInput | HookSwrInput + swrOptions?: SwrOptions<Data, FetchInput> + }): ResponseState<Data> +} + export type MutationHook< // Data obj returned by the hook and fetch operation Data, @@ -118,9 +150,7 @@ export type SwrOptions<Data, Input = null, Result = any> = ConfigInterface< */ export type Prop<T, K extends keyof T> = NonNullable<T[K]> -export type HookHandlerType = - | HookHandler<any, any, any> - | MutationHandler<any, any, any> +export type HookHandlerType = HookHandler<any, any, any> export type UseHookParameters<H extends HookHandlerType> = Parameters< Prop<H, 'useHook'> diff --git a/framework/commerce/utils/use-data.tsx b/framework/commerce/utils/use-data.tsx index 94679a0c6..fe21bd3d3 100644 --- a/framework/commerce/utils/use-data.tsx +++ b/framework/commerce/utils/use-data.tsx @@ -1,11 +1,11 @@ import useSWR, { responseInterface } from 'swr' import type { - HookHandler, HookSwrInput, HookFetchInput, - PickRequired, Fetcher, SwrOptions, + HookFetcherOptions, + HookFetcherFn, } from './types' import defineProperty from './define-property' import { CommerceError } from './errors' @@ -19,7 +19,10 @@ export type UseData = < Input extends { [k: string]: unknown } = {}, FetchInput extends HookFetchInput = {} >( - options: PickRequired<HookHandler<Data, Input, FetchInput>, 'fetcher'>, + options: { + fetchOptions: HookFetcherOptions + fetcher: HookFetcherFn<Data, FetchInput> + }, input: HookFetchInput | HookSwrInput, fetcherFn: Fetcher, swrOptions?: SwrOptions<Data, FetchInput> diff --git a/framework/commerce/utils/use-hook.ts b/framework/commerce/utils/use-hook.ts index b37c33370..830918de5 100644 --- a/framework/commerce/utils/use-hook.ts +++ b/framework/commerce/utils/use-hook.ts @@ -1,43 +1,50 @@ import { useCallback } from 'react' -import type { MutationHook } from './types' +import type { Fetcher, MutationHook, PickRequired, SWRHook } from './types' import { Provider, useCommerce } from '..' +import useData from './use-data' -export function useHookHandler<P extends Provider>( - fn: (provider: P) => MutationHook<any, any, any>, - fetcher: any -) { +export function useFetcher() { + const { providerRef, fetcherRef } = useCommerce() + return providerRef.current.fetcher ?? fetcherRef.current +} + +export function useHook< + P extends Provider, + H extends MutationHook<any, any, any> | SWRHook<any, any, any> +>(fn: (provider: P) => H) { const { providerRef } = useCommerce<P>() const provider = providerRef.current - const opts = fn(provider) - const handler = - opts.useHook ?? - (() => { - const { fetch } = useHook(fn, fetcher) - return (input: any) => fetch({ input }) - }) - - return handler + return fn(provider) } -export default function useHook<P extends Provider>( - fn: (provider: P) => MutationHook<any, any, any>, - fetcher: any +export function useSWRHook<H extends SWRHook<any, any, any>>( + hook: PickRequired<H, 'fetcher'> ) { - const { providerRef, fetcherRef } = useCommerce<P>() - const provider = providerRef.current - const opts = fn(provider) - const fetcherFn = opts.fetcher ?? fetcher - const fetchFn = provider.fetcher ?? fetcherRef.current - const fetch = useCallback( - ({ input }: { input: any }) => { - return fetcherFn({ - input, - options: opts.fetchOptions, - fetch: fetchFn, - }) - }, - [fetchFn, opts.fetchOptions] - ) + const fetcher = useFetcher() - return { fetch } + return hook.useHook({ + useData(ctx) { + const response = useData(hook, ctx?.input ?? [], fetcher, ctx?.swrOptions) + return response + }, + }) +} + +export function useMutationHook<H extends MutationHook<any, any, any>>( + hook: PickRequired<H, 'fetcher'> +) { + const fetcher = useFetcher() + + return hook.useHook({ + fetch: useCallback( + ({ input }) => { + return hook.fetcher({ + input, + options: hook.fetchOptions, + fetch: fetcher, + }) + }, + [fetcher, hook.fetchOptions] + ), + }) }