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 +export default useCart as UseCart -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 = 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 -> = async ({ options, input, fetch }) => { - return fetch({ ...options, body: input }) -} +> = mutationFetcher export type UseAddItem< H extends MutationHook = MutationHook @@ -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

= Prop< - Prop, - 'useCart' -> - -export type UseCartInput

= UseHookInput> - -export type CartResponse

= UseHookResponse< - UseCartHandler

-> - -export type UseCart

= Partial< - UseCartInput

-> extends UseCartInput

- ? (input?: UseCartInput

) => CartResponse

- : (input: UseCartInput

) => CartResponse

+export type UseCart< + H extends SWRHook = SWRHook< + Cart | null, + {}, + FetchCartInput, + { isEmpty?: boolean } + > +> = ReturnType export const fetcher: HookFetcherFn = async ({ options, @@ -38,32 +25,17 @@ export const fetcher: HookFetcherFn = async ({ return cartId ? await fetch({ ...options }) : null } -export default function useCart

( - input: UseCartInput

= {} -) { - const { providerRef, fetcherRef, cartCookie } = useCommerce

() - - 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 -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 -export const fetcher = mutationFetcher +export const fetcher: HookFetcherFn< + Cart | null, + UpdateCartItemBody +> = 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 | {}>({}) export type Provider = CommerceConfig & { fetcher: Fetcher cart?: { - useCart?: HookHandler + useCart?: SWRHook useAddItem?: MutationHook useUpdateItem?: MutationHook useRemoveItem?: MutationHook 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 = ({ options, fetch }) => fetch(options) -export const mutationFetcher: HookFetcherFn = ({ +export const mutationFetcher: HookFetcherFn = ({ 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 = Omit & K * Returns the properties in T with the properties in type K changed from optional to required */ export type PickRequired = Omit & - Required> + { + [P in K]-?: NonNullable + } /** * 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 + ): HookFunction< + Input & { swrOptions?: SwrOptions }, + ResponseState & State + > + fetchOptions: HookFetcherOptions + fetcher?: HookFetcherFn +} + +export type SWRHookContext< + Data, + FetchInput extends { [k: string]: unknown } = {} +> = { + useData(context?: { + input?: HookFetchInput | HookSwrInput + swrOptions?: SwrOptions + }): ResponseState +} + export type MutationHook< // Data obj returned by the hook and fetch operation Data, @@ -118,9 +150,7 @@ export type SwrOptions = ConfigInterface< */ export type Prop = NonNullable -export type HookHandlerType = - | HookHandler - | MutationHandler +export type HookHandlerType = HookHandler export type UseHookParameters = Parameters< Prop 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, 'fetcher'>, + options: { + fetchOptions: HookFetcherOptions + fetcher: HookFetcherFn + }, input: HookFetchInput | HookSwrInput, fetcherFn: Fetcher, swrOptions?: SwrOptions 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

( - fn: (provider: P) => MutationHook, - fetcher: any -) { +export function useFetcher() { + const { providerRef, fetcherRef } = useCommerce() + return providerRef.current.fetcher ?? fetcherRef.current +} + +export function useHook< + P extends Provider, + H extends MutationHook | SWRHook +>(fn: (provider: P) => H) { const { providerRef } = useCommerce

() 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

( - fn: (provider: P) => MutationHook, - fetcher: any +export function useSWRHook>( + hook: PickRequired ) { - const { providerRef, fetcherRef } = useCommerce

() - 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>( + hook: PickRequired +) { + const fetcher = useFetcher() + + return hook.useHook({ + fetch: useCallback( + ({ input }) => { + return hook.fetcher({ + input, + options: hook.fetchOptions, + fetch: fetcher, + }) + }, + [fetcher, hook.fetchOptions] + ), + }) }