From bafb8a4479ee65b8cd5cb9c40b1ebc5caed98aae Mon Sep 17 00:00:00 2001 From: Luis Alvarez Date: Thu, 21 Jan 2021 21:19:53 -0500 Subject: [PATCH] Added useResponse hook --- framework/bigcommerce/cart/use-cart.tsx | 33 +++++++--------- framework/commerce/cart/use-cart.tsx | 3 -- framework/commerce/utils/types.ts | 2 + framework/commerce/utils/use-data.tsx | 2 +- framework/commerce/utils/use-response.tsx | 40 ++++++++++++++++++++ framework/commerce/wishlist/use-wishlist.tsx | 1 - 6 files changed, 57 insertions(+), 24 deletions(-) create mode 100644 framework/commerce/utils/use-response.tsx diff --git a/framework/bigcommerce/cart/use-cart.tsx b/framework/bigcommerce/cart/use-cart.tsx index 708db68c5..c1a9bed7a 100644 --- a/framework/bigcommerce/cart/use-cart.tsx +++ b/framework/bigcommerce/cart/use-cart.tsx @@ -1,10 +1,9 @@ import { normalizeCart } from '../lib/normalize' import type { HookFetcher } from '@commerce/utils/types' import type { SwrOptions } from '@commerce/utils/use-data' -import defineProperty from '@commerce/utils/define-property' +import useResponse from '@commerce/utils/use-response' import useCommerceCart, { CartInput } from '@commerce/cart/use-cart' import type { Cart as BigCommerceCart } from '../api/cart' -import update from '@framework/lib/immutability' const defaultOpts = { url: '/api/bigcommerce/cart', @@ -30,25 +29,21 @@ export function extendHook( revalidateOnFocus: false, ...swrOptions, }) - - // Uses a getter to only calculate the prop when required - // response.data is also a getter and it's better to not trigger it early - if (!('isEmpty' in response)) { - defineProperty(response, 'isEmpty', { - get() { - return Object.values(response.data?.line_items ?? {}).every( - (items) => !items.length - ) + const res = useResponse(response, { + normalizer: normalizeCart, + descriptors: { + isEmpty: { + get() { + return Object.values(response.data?.line_items ?? {}).every( + (items) => !items.length + ) + }, + enumerable: true, }, - set: (x) => x, - }) - } + }, + }) - return response.data - ? update(response, { - data: { $set: normalizeCart(response.data) }, - }) - : response + return res } useCart.extend = extendHook diff --git a/framework/commerce/cart/use-cart.tsx b/framework/commerce/cart/use-cart.tsx index 513c041ea..c0fd10a5e 100644 --- a/framework/commerce/cart/use-cart.tsx +++ b/framework/commerce/cart/use-cart.tsx @@ -1,4 +1,3 @@ -import type { responseInterface } from 'swr' import Cookies from 'js-cookie' import type { HookInput, HookFetcher, HookFetcherOptions } from '../utils/types' import useData, { ResponseState, SwrOptions } from '../utils/use-data' @@ -17,12 +16,10 @@ export default function useCart( swrOptions?: SwrOptions ): CartResponse { 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) return response diff --git a/framework/commerce/utils/types.ts b/framework/commerce/utils/types.ts index b983d3391..2cd838964 100644 --- a/framework/commerce/utils/types.ts +++ b/framework/commerce/utils/types.ts @@ -22,3 +22,5 @@ export type HookFetcherOptions = { } export type HookInput = [string, string | number | boolean | undefined][] + +export type Override = Omit & K diff --git a/framework/commerce/utils/use-data.tsx b/framework/commerce/utils/use-data.tsx index 00d7c8c31..a921eb529 100644 --- a/framework/commerce/utils/use-data.tsx +++ b/framework/commerce/utils/use-data.tsx @@ -64,7 +64,7 @@ const useData: UseData = (options, input, fetcherFn, swrOptions) => { get() { return response.data === undefined }, - set: (x) => x, + enumerable: true, }) } diff --git a/framework/commerce/utils/use-response.tsx b/framework/commerce/utils/use-response.tsx new file mode 100644 index 000000000..f3a1ed071 --- /dev/null +++ b/framework/commerce/utils/use-response.tsx @@ -0,0 +1,40 @@ +import { useMemo } from 'react' +import { responseInterface } from 'swr' +import { CommerceError } from './errors' +import { Override } from './types' + +export type UseResponseOptions< + D, + R extends responseInterface +> = { + descriptors?: PropertyDescriptorMap + normalizer?: (data: R['data']) => D +} + +export type UseResponse = >( + response: R, + options: UseResponseOptions +) => D extends object ? Override : R + +const useResponse: UseResponse = (response, { descriptors, normalizer }) => { + const memoizedResponse = useMemo( + () => + Object.create(response, { + ...descriptors, + ...(normalizer + ? { + data: { + get() { + return normalizer(response.data) + }, + enumerable: true, + }, + } + : {}), + }), + [response] + ) + return memoizedResponse +} + +export default useResponse diff --git a/framework/commerce/wishlist/use-wishlist.tsx b/framework/commerce/wishlist/use-wishlist.tsx index 62b1c8702..df8bebe5c 100644 --- a/framework/commerce/wishlist/use-wishlist.tsx +++ b/framework/commerce/wishlist/use-wishlist.tsx @@ -1,4 +1,3 @@ -import type { responseInterface } from 'swr' import type { HookInput, HookFetcher, HookFetcherOptions } from '../utils/types' import useData, { ResponseState, SwrOptions } from '../utils/use-data'