From aab2e7f7cc97040daa05bdd4124125d0f6b5c7c0 Mon Sep 17 00:00:00 2001 From: Luis Alvarez <luis@vercel.com> Date: Mon, 8 Feb 2021 10:52:35 -0500 Subject: [PATCH] Replace use-cart with the new hook --- components/common/UserNav/UserNav.tsx | 7 +++- framework/bigcommerce/cart/use-cart.tsx | 54 ++----------------------- framework/bigcommerce/index.tsx | 10 +++-- framework/commerce/cart/use-cart-2.tsx | 28 ------------- framework/commerce/cart/use-cart.tsx | 54 ++++++++++++++++--------- framework/commerce/cart/use-fake.tsx | 9 +++-- framework/commerce/index.tsx | 6 ++- framework/commerce/utils/use-data-2.ts | 7 ++-- 8 files changed, 66 insertions(+), 109 deletions(-) delete mode 100644 framework/commerce/cart/use-cart-2.tsx diff --git a/components/common/UserNav/UserNav.tsx b/components/common/UserNav/UserNav.tsx index f8e6373d9..7048cc468 100644 --- a/components/common/UserNav/UserNav.tsx +++ b/components/common/UserNav/UserNav.tsx @@ -1,7 +1,10 @@ import { FC } from 'react' import Link from 'next/link' import cn from 'classnames' +import type { BigcommerceProvider } from '@framework' +import { LineItem } from '@framework/types' import useCart from '@framework/cart/use-cart' +import useFake from '@commerce/cart/use-fake' import useCustomer from '@framework/customer/use-customer' import { Heart, Bag } from '@components/icons' import { useUI } from '@components/ui/context' @@ -15,12 +18,14 @@ interface Props { const countItem = (count: number, item: LineItem) => count + item.quantity -const UserNav: FC<Props> = ({ className, children }) => { +const UserNav: FC<Props> = ({ className }) => { const { data } = useCart() const { data: customer } = useCustomer() const { toggleSidebar, closeSidebarIfPresent, openModal } = useUI() const itemsCount = data?.lineItems.reduce(countItem, 0) ?? 0 + const x = useFake<BigcommerceProvider>() + return ( <nav className={cn(s.root, className)}> <div className={s.mainContainer}> diff --git a/framework/bigcommerce/cart/use-cart.tsx b/framework/bigcommerce/cart/use-cart.tsx index afa37ec98..8b984ab9f 100644 --- a/framework/bigcommerce/cart/use-cart.tsx +++ b/framework/bigcommerce/cart/use-cart.tsx @@ -1,52 +1,4 @@ -import type { HookFetcher } from '@commerce/utils/types' -import type { SwrOptions } from '@commerce/utils/use-data' -import useResponse from '@commerce/utils/use-response' -import useCommerceCart, { CartInput } from '@commerce/cart/use-cart' -import { normalizeCart } from '../lib/normalize' -import type { Cart, BigcommerceCart } from '../types' +import useCommerceCart, { UseCart } from '@commerce/cart/use-cart' +import { BigcommerceProvider } from '..' -const defaultOpts = { - url: '/api/bigcommerce/cart', - method: 'GET', -} - -export const fetcher: HookFetcher<Cart | null, CartInput> = async ( - options, - { cartId }, - fetch -) => { - const data = cartId - ? await fetch<BigcommerceCart>({ ...defaultOpts, ...options }) - : null - return data && normalizeCart(data) -} - -export function extendHook( - customFetcher: typeof fetcher, - swrOptions?: SwrOptions<Cart | null, CartInput> -) { - const useCart = () => { - const response = useCommerceCart(defaultOpts, [], customFetcher, { - revalidateOnFocus: false, - ...swrOptions, - }) - const res = useResponse(response, { - descriptors: { - isEmpty: { - get() { - return (response.data?.lineItems.length ?? 0) <= 0 - }, - enumerable: true, - }, - }, - }) - - return res - } - - useCart.extend = extendHook - - return useCart -} - -export default extendHook(fetcher) +export default useCommerceCart as UseCart<BigcommerceProvider> diff --git a/framework/bigcommerce/index.tsx b/framework/bigcommerce/index.tsx index 268b0bd02..e1584c9e0 100644 --- a/framework/bigcommerce/index.tsx +++ b/framework/bigcommerce/index.tsx @@ -49,14 +49,18 @@ const fetcher: Fetcher<any> = async ({ throw await getError(res) } -const useCart: HookHandler<Cart, CartInput> = { +const useCart: HookHandler<Cart, CartInput, any, any, { isEmpty?: boolean }> = { fetchOptions: { url: '/api/bigcommerce/cart', method: 'GET', }, - fetcher(context) { - return undefined as any + swrOptions: { + revalidateOnFocus: false, }, + // fetcher(context) { + // return undefined as any + // }, + normalizer: normalizeCart, onResponse(response) { return Object.create(response, { isEmpty: { diff --git a/framework/commerce/cart/use-cart-2.tsx b/framework/commerce/cart/use-cart-2.tsx deleted file mode 100644 index cfeb85c87..000000000 --- a/framework/commerce/cart/use-cart-2.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import Cookies from 'js-cookie' -import type { HookInput, HookFetcher, HookFetcherOptions } from '../utils/types' -import useData, { ResponseState, SwrOptions } from '../utils/use-data' -import type { Cart } from '../types' -import { useCommerce } from '..' - -export type CartResponse<Data> = ResponseState<Data> & { isEmpty?: boolean } - -// Input expected by the `useCart` hook -export type CartInput = { - cartId?: Cart['id'] -} - -export default function useCart<Data extends Cart | null>( - options: HookFetcherOptions, - input: HookInput, - fetcherFn: HookFetcher<Data, CartInput>, - swrOptions?: SwrOptions<Data, CartInput> -): CartResponse<Data> { - const { providerRef, cartCookie } = useCommerce() - const fetcher: typeof fetcherFn = (options, input, fetch) => { - input.cartId = Cookies.get(cartCookie) - return fetcherFn(options, input, fetch) - } - const response = useData(options, input, fetcher, swrOptions) - - return response -} diff --git a/framework/commerce/cart/use-cart.tsx b/framework/commerce/cart/use-cart.tsx index 0a7ba49ee..77271b593 100644 --- a/framework/commerce/cart/use-cart.tsx +++ b/framework/commerce/cart/use-cart.tsx @@ -1,28 +1,46 @@ +import { useMemo } from 'react' import Cookies from 'js-cookie' -import type { HookInput, HookFetcher, HookFetcherOptions } from '../utils/types' -import useData, { ResponseState, SwrOptions } from '../utils/use-data' import type { Cart } from '../types' -import { useCommerce } from '..' - -export type CartResponse<Data> = ResponseState<Data> & { isEmpty?: boolean } +import type { HookFetcherFn } from '../utils/types' +import useData from '../utils/use-data-2' +import { Provider, useCommerce } from '..' // Input expected by the `useCart` hook export type CartInput = { cartId?: Cart['id'] } -export default function useCart<Data extends Cart | null>( - options: HookFetcherOptions, - input: HookInput, - fetcherFn: HookFetcher<Data, CartInput>, - swrOptions?: SwrOptions<Data, CartInput> -): CartResponse<Data> { - const { cartCookie } = useCommerce() - const fetcher: typeof fetcherFn = (options, input, fetch) => { - input.cartId = Cookies.get(cartCookie) - return fetcherFn(options, input, fetch) - } - const response = useData(options, input, fetcher, swrOptions) +export type CartResponse<P extends Provider> = ReturnType< + NonNullable<NonNullable<NonNullable<P['cart']>['useCart']>['onResponse']> +> - return response +export type UseCart<P extends Provider> = () => CartResponse<P> + +export const fetcher: HookFetcherFn<Cart | null, CartInput> = async ({ + options, + input: { cartId }, + fetch, + normalize, +}) => { + const data = cartId ? await fetch({ ...options }) : null + return data && normalize ? normalize(data) : data +} + +export default function useCart<P extends Provider>() { + const { providerRef, cartCookie } = useCommerce<P>() + + const provider = providerRef.current + const opts = provider.cart?.useCart + const fetcherFn = opts?.fetcher ?? fetcher + const wrapper: typeof fetcher = (context) => { + context.input.cartId = Cookies.get(cartCookie) + return fetcherFn(context) + } + const response = useData(opts!, [], wrapper, opts?.swrOptions) + const memoizedResponse = useMemo( + () => (opts?.onResponse ? opts.onResponse(response) : response), + [response] + ) + + return memoizedResponse as CartResponse<P> } diff --git a/framework/commerce/cart/use-fake.tsx b/framework/commerce/cart/use-fake.tsx index 324e1c4d9..2f0083d10 100644 --- a/framework/commerce/cart/use-fake.tsx +++ b/framework/commerce/cart/use-fake.tsx @@ -10,7 +10,11 @@ export type CartInput = { cartId?: Cart['id'] } -const fetcher: HookFetcherFn<Cart | null, CartInput> = async ({ +export type CartResponse<P extends Provider> = ReturnType< + NonNullable<NonNullable<NonNullable<P['cart']>['useCart']>['onResponse']> +> + +export const fetcher: HookFetcherFn<Cart | null, CartInput> = async ({ options, input: { cartId }, fetch, @@ -31,12 +35,11 @@ export default function useFake<P extends Provider>() { context.input.cartId = Cookies.get(cartCookie) return fetcherFn(context) } - const response = useData(options, [], wrapper, opts?.swrOptions) const memoizedResponse = useMemo( () => (opts?.onResponse ? opts.onResponse(response) : response), [response] ) - return memoizedResponse + return memoizedResponse as CartResponse<P> } diff --git a/framework/commerce/index.tsx b/framework/commerce/index.tsx index 91a1be2e0..e52aa539d 100644 --- a/framework/commerce/index.tsx +++ b/framework/commerce/index.tsx @@ -21,9 +21,11 @@ export type Provider = CommerceConfig & { cartNormalizer(data: any): Cart } -export type HookHandler<Data, Input, Result = any, Body = any> = { +export type HookHandler<Data, Input, Result = any, Body = any, State = {}> = { swrOptions?: SwrOptions<Data | null, Input, Result> - onResponse?(response: ResponseState<Data | null>): ResponseState<Data | null> + onResponse?( + response: ResponseState<Data | null> + ): ResponseState<Data | null> & State onMutation?: any fetchOptions?: HookFetcherOptions } & ( diff --git a/framework/commerce/utils/use-data-2.ts b/framework/commerce/utils/use-data-2.ts index 69eb223b0..ed3218840 100644 --- a/framework/commerce/utils/use-data-2.ts +++ b/framework/commerce/utils/use-data-2.ts @@ -7,7 +7,7 @@ import type { } from './types' import defineProperty from './define-property' import { CommerceError } from './errors' -import { useCommerce } from '..' +import { HookHandler, useCommerce } from '..' export type SwrOptions<Data, Input = null, Result = any> = ConfigInterface< Data, @@ -20,7 +20,7 @@ export type ResponseState<Result> = responseInterface<Result, CommerceError> & { } export type UseData = <Data = any, Input = null, Result = any>( - options: HookFetcherOptions | (() => HookFetcherOptions | null), + options: HookHandler<Data, Input, Result>, input: HookInput, fetcherFn: HookFetcherFn<Data, Input, Result>, swrOptions?: SwrOptions<Data, Input, Result> @@ -43,6 +43,7 @@ const useData: UseData = (options, input, fetcherFn, swrOptions) => { return obj }, {}), fetch: fetcherRef.current, + normalize: options.normalizer, }) } catch (error) { // SWR will not log errors, but any error that's not an instance @@ -55,7 +56,7 @@ const useData: UseData = (options, input, fetcherFn, swrOptions) => { } const response = useSWR( () => { - const opts = typeof options === 'function' ? options() : options + const opts = options.fetchOptions return opts ? [opts.url, opts.query, opts.method, ...input.map((e) => e[1])] : null