mirror of
https://github.com/vercel/commerce.git
synced 2025-03-14 22:42:33 +00:00
Add loading state for data hooks
This commit is contained in:
parent
4a268e738d
commit
10318b2fab
@ -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 useCommerceCart, { CartInput } from '@commerce/cart/use-cart'
|
||||
import type { Cart as BigCommerceCart } from '../api/cart'
|
||||
import update from "@framework/lib/immutability"
|
||||
import update from '@framework/lib/immutability'
|
||||
|
||||
const defaultOpts = {
|
||||
url: '/api/bigcommerce/cart',
|
||||
@ -13,7 +12,7 @@ const defaultOpts = {
|
||||
|
||||
type UseCartResponse = BigCommerceCart & Cart
|
||||
|
||||
export const fetcher: HookFetcher<UseCartResponse | null , CartInput> = (
|
||||
export const fetcher: HookFetcher<UseCartResponse | null, CartInput> = (
|
||||
options,
|
||||
{ cartId },
|
||||
fetch
|
||||
@ -42,11 +41,11 @@ export function extendHook(
|
||||
set: (x) => x,
|
||||
})
|
||||
|
||||
|
||||
|
||||
return response.data ? update(response, {
|
||||
data: { $set: normalizeCart(response.data ) }
|
||||
}) : response
|
||||
return response.data
|
||||
? update(response, {
|
||||
data: { $set: normalizeCart(response.data) },
|
||||
})
|
||||
: response
|
||||
}
|
||||
|
||||
useCart.extend = extendHook
|
||||
|
@ -1,14 +1,13 @@
|
||||
import update from "@framework/lib/immutability"
|
||||
import { Cart, CartItem, Product } from '../../types'
|
||||
import update from '@framework/lib/immutability'
|
||||
|
||||
function normalizeProductOption(productOption:any) {
|
||||
function normalizeProductOption(productOption: any) {
|
||||
const {
|
||||
node: {
|
||||
entityId,
|
||||
values: { edges },
|
||||
...rest
|
||||
},
|
||||
} = productOption;
|
||||
} = productOption
|
||||
|
||||
return {
|
||||
id: entityId,
|
||||
@ -30,52 +29,52 @@ export function normalizeProduct(productNode: any): Product {
|
||||
return update(productNode, {
|
||||
id: { $set: String(id) },
|
||||
images: {
|
||||
$apply: ({edges} : any) => edges?.map(
|
||||
({ node: { urlOriginal, altText, ...rest } }: any) => ({
|
||||
$apply: ({ edges }: any) =>
|
||||
edges?.map(({ node: { urlOriginal, altText, ...rest } }: any) => ({
|
||||
url: urlOriginal,
|
||||
alt: altText,
|
||||
...rest,
|
||||
})
|
||||
)
|
||||
},
|
||||
})),
|
||||
},
|
||||
variants: {
|
||||
$apply: ({edges} : any) => edges?.map(
|
||||
({ node: { entityId, productOptions, ...rest } }: any) => ({
|
||||
$apply: ({ edges }: any) =>
|
||||
edges?.map(({ node: { entityId, productOptions, ...rest } }: any) => ({
|
||||
id: entityId,
|
||||
options: productOptions?.edges
|
||||
? productOptions.edges.map(normalizeProductOption)
|
||||
: [],
|
||||
...rest,
|
||||
})
|
||||
)
|
||||
})),
|
||||
},
|
||||
options: {
|
||||
$set: productOptions.edges ? productOptions?.edges.map(normalizeProductOption) : []
|
||||
$set: productOptions.edges
|
||||
? productOptions?.edges.map(normalizeProductOption)
|
||||
: [],
|
||||
},
|
||||
brand: {
|
||||
$apply:(brand : any) => brand?.entityId ? brand?.entityId : null,
|
||||
},
|
||||
slug: {
|
||||
$set: path?.replace(/^\/+|\/+$/g, '')
|
||||
$apply: (brand: any) => (brand?.entityId ? brand?.entityId : null),
|
||||
},
|
||||
slug: {
|
||||
$set: path?.replace(/^\/+|\/+$/g, ''),
|
||||
},
|
||||
price: {
|
||||
$set: {
|
||||
value: prices?.price.value,
|
||||
currencyCode: prices?.price.currencyCode,
|
||||
}
|
||||
},
|
||||
$unset: ['entityId']
|
||||
},
|
||||
},
|
||||
$unset: ['entityId'],
|
||||
})
|
||||
}
|
||||
|
||||
export function normalizeCart(data: any): Cart {
|
||||
return update(data, {
|
||||
$auto: {
|
||||
items: { $set: data?.line_items?.physical_items?.map(itemsToProducts)},
|
||||
items: { $set: data?.line_items?.physical_items?.map(itemsToProducts) },
|
||||
subTotal: { $set: data?.base_amount },
|
||||
total: { $set: data?.cart_amount }
|
||||
total: { $set: data?.cart_amount },
|
||||
},
|
||||
$unset: ['created_time', 'coupons', 'line_items', 'email']
|
||||
$unset: ['created_time', 'coupons', 'line_items', 'email'],
|
||||
})
|
||||
}
|
||||
|
||||
@ -92,24 +91,28 @@ function itemsToProducts(item: any): CartItem {
|
||||
extended_list_price,
|
||||
extended_sale_price,
|
||||
...rest
|
||||
} = item;
|
||||
} = item
|
||||
|
||||
return update(item, {
|
||||
$auto: {
|
||||
prices: {
|
||||
$auto: {
|
||||
listPrice: { $set: list_price },
|
||||
salePrice: { $set: sale_price } ,
|
||||
salePrice: { $set: sale_price },
|
||||
extendedListPrice: { $set: extended_list_price },
|
||||
extendedSalePrice: { $set: extended_sale_price },
|
||||
}
|
||||
},
|
||||
},
|
||||
images: {
|
||||
$set: [
|
||||
{
|
||||
alt: name,
|
||||
url: image_url,
|
||||
},
|
||||
],
|
||||
},
|
||||
images: { $set: [{
|
||||
alt: name,
|
||||
url: image_url
|
||||
}]},
|
||||
productId: { $set: product_id },
|
||||
variantId: { $set: variant_id }
|
||||
}
|
||||
variantId: { $set: variant_id },
|
||||
},
|
||||
})
|
||||
}
|
||||
|
@ -1,12 +1,10 @@
|
||||
import type { responseInterface } from 'swr'
|
||||
import Cookies from 'js-cookie'
|
||||
import type { HookInput, HookFetcher, HookFetcherOptions } from '../utils/types'
|
||||
import useData, { SwrOptions } from '../utils/use-data'
|
||||
import useData, { ResponseState, SwrOptions } from '../utils/use-data'
|
||||
import { useCommerce } from '..'
|
||||
|
||||
export type CartResponse<Result> = responseInterface<Result, Error> & {
|
||||
isEmpty: boolean
|
||||
}
|
||||
export type CartResponse<Result> = ResponseState<Result> & { isEmpty: boolean }
|
||||
|
||||
export type CartInput = {
|
||||
cartId: Cart['id']
|
||||
@ -17,7 +15,7 @@ export default function useCart<Result>(
|
||||
input: HookInput,
|
||||
fetcherFn: HookFetcher<Result, CartInput>,
|
||||
swrOptions?: SwrOptions<Result, CartInput>
|
||||
) {
|
||||
): CartResponse<Result> {
|
||||
const { cartCookie } = useCommerce()
|
||||
|
||||
const fetcher: typeof fetcherFn = (options, input, fetch) => {
|
||||
@ -27,5 +25,5 @@ export default function useCart<Result>(
|
||||
|
||||
const response = useData(options, input, fetcher, swrOptions)
|
||||
|
||||
return Object.assign(response, { isEmpty: true }) as CartResponse<Result>
|
||||
return Object.assign(response, { isEmpty: true })
|
||||
}
|
||||
|
37
framework/commerce/utils/define-property.ts
Normal file
37
framework/commerce/utils/define-property.ts
Normal file
@ -0,0 +1,37 @@
|
||||
// Taken from https://fettblog.eu/typescript-assertion-signatures/
|
||||
|
||||
type InferValue<Prop extends PropertyKey, Desc> = Desc extends {
|
||||
get(): any
|
||||
value: any
|
||||
}
|
||||
? never
|
||||
: Desc extends { value: infer T }
|
||||
? Record<Prop, T>
|
||||
: Desc extends { get(): infer T }
|
||||
? Record<Prop, T>
|
||||
: never
|
||||
|
||||
type DefineProperty<
|
||||
Prop extends PropertyKey,
|
||||
Desc extends PropertyDescriptor
|
||||
> = Desc extends { writable: any; set(val: any): any }
|
||||
? never
|
||||
: Desc extends { writable: any; get(): any }
|
||||
? never
|
||||
: Desc extends { writable: false }
|
||||
? Readonly<InferValue<Prop, Desc>>
|
||||
: Desc extends { writable: true }
|
||||
? InferValue<Prop, Desc>
|
||||
: Readonly<InferValue<Prop, Desc>>
|
||||
|
||||
export default function defineProperty<
|
||||
Obj extends object,
|
||||
Key extends PropertyKey,
|
||||
PDesc extends PropertyDescriptor
|
||||
>(
|
||||
obj: Obj,
|
||||
prop: Key,
|
||||
val: PDesc
|
||||
): asserts obj is Obj & DefineProperty<Key, PDesc> {
|
||||
Object.defineProperty(obj, prop, val)
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
import useSWR, { ConfigInterface, responseInterface } from 'swr'
|
||||
import type { HookInput, HookFetcher, HookFetcherOptions } from './types'
|
||||
import defineProperty from './define-property'
|
||||
import { CommerceError } from './errors'
|
||||
import { useCommerce } from '..'
|
||||
|
||||
@ -9,12 +10,16 @@ export type SwrOptions<Result, Input = null> = ConfigInterface<
|
||||
HookFetcher<Result, Input>
|
||||
>
|
||||
|
||||
export type ResponseState<Result> = responseInterface<Result, CommerceError> & {
|
||||
isLoading: boolean
|
||||
}
|
||||
|
||||
export type UseData = <Result = any, Input = null>(
|
||||
options: HookFetcherOptions | (() => HookFetcherOptions | null),
|
||||
input: HookInput,
|
||||
fetcherFn: HookFetcher<Result, Input>,
|
||||
swrOptions?: SwrOptions<Result, Input>
|
||||
) => responseInterface<Result, CommerceError>
|
||||
) => ResponseState<Result>
|
||||
|
||||
const useData: UseData = (options, input, fetcherFn, swrOptions) => {
|
||||
const { fetcherRef } = useCommerce()
|
||||
@ -54,6 +59,13 @@ const useData: UseData = (options, input, fetcherFn, swrOptions) => {
|
||||
swrOptions
|
||||
)
|
||||
|
||||
defineProperty(response, 'isLoading', {
|
||||
get() {
|
||||
return response.data === undefined
|
||||
},
|
||||
set: (x) => x,
|
||||
})
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
import type { responseInterface } from 'swr'
|
||||
import type { HookInput, HookFetcher, HookFetcherOptions } from '../utils/types'
|
||||
import useData, { SwrOptions } from '../utils/use-data'
|
||||
import useData, { ResponseState, SwrOptions } from '../utils/use-data'
|
||||
|
||||
export type WishlistResponse<Result> = responseInterface<Result, Error> & {
|
||||
export type WishlistResponse<Result> = ResponseState<Result> & {
|
||||
isEmpty: boolean
|
||||
}
|
||||
|
||||
@ -11,7 +11,7 @@ export default function useWishlist<Result, Input = null>(
|
||||
input: HookInput,
|
||||
fetcherFn: HookFetcher<Result, Input>,
|
||||
swrOptions?: SwrOptions<Result, Input>
|
||||
) {
|
||||
): WishlistResponse<Result> {
|
||||
const response = useData(options, input, fetcherFn, swrOptions)
|
||||
return Object.assign(response, { isEmpty: true }) as WishlistResponse<Result>
|
||||
return Object.assign(response, { isEmpty: true })
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user