diff --git a/lib/bigcommerce/cart/use-add-item.tsx b/lib/bigcommerce/cart/use-add-item.tsx index 9285073da..924eeb6eb 100644 --- a/lib/bigcommerce/cart/use-add-item.tsx +++ b/lib/bigcommerce/cart/use-add-item.tsx @@ -1,3 +1,4 @@ +import { useCallback } from 'react' import type { Fetcher } from '@lib/commerce' import { default as useCartAddItem } from '@lib/commerce/cart/use-add-item' import type { ItemBody, AddItemBody } from '../api/cart' @@ -21,11 +22,13 @@ function fetcher(fetch: Fetcher, { item }: AddItemBody) { export default function useAddItem() { const { mutate } = useCart() const fn = useCartAddItem(fetcher) - const addItem = async (input: UpdateItemInput) => { - const data = await fn({ item: input }) - await mutate(data, false) - return data - } - return addItem + return useCallback( + async function addItem(input: UpdateItemInput) { + const data = await fn({ item: input }) + await mutate(data, false) + return data + }, + [fn, mutate] + ) } diff --git a/lib/bigcommerce/cart/use-remove-item.tsx b/lib/bigcommerce/cart/use-remove-item.tsx index adfa19a3b..8334d4304 100644 --- a/lib/bigcommerce/cart/use-remove-item.tsx +++ b/lib/bigcommerce/cart/use-remove-item.tsx @@ -1,3 +1,4 @@ +import { useCallback } from 'react' import type { Fetcher } from '@lib/commerce' import { default as useCartRemoveItem } from '@lib/commerce/cart/use-remove-item' import type { RemoveItemBody } from '../api/cart' @@ -21,11 +22,13 @@ export function fetcher( export default function useRemoveItem(item?: any) { const { mutate } = useCart() const fn = useCartRemoveItem(fetcher) - const removeItem = async (input: RemoveItemInput) => { - const data = await fn({ itemId: input.id ?? item?.id }) - await mutate(data, false) - return data - } - return removeItem + return useCallback( + async function removeItem(input: RemoveItemInput) { + const data = await fn({ itemId: input.id ?? item?.id }) + await mutate(data, false) + return data + }, + [fn, mutate] + ) } diff --git a/lib/bigcommerce/cart/use-update-item.tsx b/lib/bigcommerce/cart/use-update-item.tsx index 0896c310f..792f9f61f 100644 --- a/lib/bigcommerce/cart/use-update-item.tsx +++ b/lib/bigcommerce/cart/use-update-item.tsx @@ -1,3 +1,5 @@ +import { useCallback } from 'react' +import debounce from 'lodash.debounce' import type { Fetcher } from '@lib/commerce' import { default as useCartUpdateItem } from '@lib/commerce/cart/use-update-item' import type { ItemBody, UpdateItemBody } from '../api/cart' @@ -26,21 +28,23 @@ function fetcher( }) } -export default function useUpdateItem(item?: any) { +export default function useUpdateItem(item?: any, cfg?: { wait?: number }) { const { mutate } = useCart() const fn = useCartUpdateItem(fetcher) - const updateItem = async (input: UpdateItemInput) => { - const data = await fn({ - itemId: input.id ?? item?.id, - item: { - productId: input.productId ?? item?.product_id, - variantId: input.productId ?? item?.variant_id, - quantity: input.quantity, - }, - }) - await mutate(data, false) - return data - } - return updateItem + return useCallback( + debounce(async (input: UpdateItemInput) => { + const data = await fn({ + itemId: input.id ?? item?.id, + item: { + productId: input.productId ?? item?.product_id, + variantId: input.productId ?? item?.variant_id, + quantity: input.quantity, + }, + }) + await mutate(data, false) + return data + }, cfg?.wait ?? 500), + [fn, mutate] + ) } diff --git a/lib/commerce/cart/index.tsx b/lib/commerce/cart/index.tsx index b18324090..bf2a6e3ed 100644 --- a/lib/commerce/cart/index.tsx +++ b/lib/commerce/cart/index.tsx @@ -19,10 +19,9 @@ const CartProvider: FC = ({ children, query, url }) => { } function useCart() { - const { fetcher: fetch, cartCookie } = useCommerce() - const fetcher = (url?: string, query?: string) => { - return Cookies.get(cartCookie) ? fetch({ url, query }) : null - } + const { fetcherRef, cartCookie } = useCommerce() + const fetcher = (url?: string, query?: string) => + Cookies.get(cartCookie) ? fetcherRef.current({ url, query }) : null const { url, query } = useContext(CartContext) const response = useSWR([url, query], fetcher, { revalidateOnFocus: false, diff --git a/lib/commerce/cart/use-add-item.tsx b/lib/commerce/cart/use-add-item.tsx index 105350cb2..6433e4393 100644 --- a/lib/commerce/cart/use-add-item.tsx +++ b/lib/commerce/cart/use-add-item.tsx @@ -1,11 +1,15 @@ +import { useCallback } from 'react' import { Fetcher, useCommerce } from '..' export default function useAddItem( fetcher: (fetch: Fetcher, input: Input) => T | Promise ) { - const { fetcher: fetch } = useCommerce() + const { fetcherRef } = useCommerce() - return async function addItem(input: Input) { - return fetcher(fetch, input) - } + return useCallback( + function addItem(input: Input) { + return fetcher(fetcherRef.current, input) + }, + [fetcher] + ) } diff --git a/lib/commerce/cart/use-remove-item.tsx b/lib/commerce/cart/use-remove-item.tsx index e7c529388..5543a1847 100644 --- a/lib/commerce/cart/use-remove-item.tsx +++ b/lib/commerce/cart/use-remove-item.tsx @@ -1,11 +1,15 @@ +import { useCallback } from 'react' import { Fetcher, useCommerce } from '..' export default function useRemoveItem( fetcher: (fetch: Fetcher, input: Input) => T | Promise ) { - const { fetcher: fetch } = useCommerce() + const { fetcherRef } = useCommerce() - return async function removeItem(input: Input) { - return fetcher(fetch, input) - } + return useCallback( + function removeItem(input: Input) { + return fetcher(fetcherRef.current, input) + }, + [fetcher] + ) } diff --git a/lib/commerce/cart/use-update-item.tsx b/lib/commerce/cart/use-update-item.tsx index 2d076129a..d244f5f27 100644 --- a/lib/commerce/cart/use-update-item.tsx +++ b/lib/commerce/cart/use-update-item.tsx @@ -1,11 +1,15 @@ +import { useCallback } from 'react' import { Fetcher, useCommerce } from '..' export default function useUpdateItem( fetcher: (fetch: Fetcher, input: Input) => T | Promise ) { - const { fetcher: fetch } = useCommerce() + const { fetcherRef } = useCommerce() - return async function updateItem(input: Input) { - return fetcher(fetch, input) - } + return useCallback( + function updateItem(input: Input) { + return fetcher(fetcherRef.current, input) + }, + [fetcher] + ) } diff --git a/lib/commerce/index.tsx b/lib/commerce/index.tsx index bc125f210..0cdb43851 100644 --- a/lib/commerce/index.tsx +++ b/lib/commerce/index.tsx @@ -1,14 +1,21 @@ -import { createContext, ReactNode, useContext, useMemo } from 'react' +import { + ReactNode, + MutableRefObject, + createContext, + useContext, + useMemo, + useRef, +} from 'react' const Commerce = createContext(null) export type CommerceProps = { children?: ReactNode - config: CommerceConfig + config: { fetcher: Fetcher } & CommerceConfig } export type CommerceConfig = { - fetcher: Fetcher + fetcherRef: MutableRefObject locale: string cartCookie: string } @@ -28,17 +35,16 @@ export function CommerceProvider({ children, config }: CommerceProps) { throw new Error('CommerceProvider requires a valid config object') } + const fetcherRef = useRef(config.fetcher) // Because the config is an object, if the parent re-renders this provider // will re-render every consumer unless we memoize the config const cfg = useMemo( () => ({ - fetcher: config.fetcher, + fetcherRef, locale: config.locale, cartCookie: config.cartCookie, }), - // Even though the fetcher is a function, it's never expected to be - // added dynamically (We should say that on the docs for this hook) - [config.fetcher, config.locale, config.cartCookie] + [config.locale, config.cartCookie] ) return {children} diff --git a/package.json b/package.json index df732c6fe..0e6030922 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "cookie": "^0.4.1", "js-cookie": "^2.2.1", "lodash": "^4.17.20", + "lodash.debounce": "^4.0.8", "next": "^9.5.4-canary.23", "postcss-nesting": "^7.0.1", "react": "^16.13.1", @@ -39,6 +40,7 @@ "@graphql-codegen/typescript-operations": "^1.17.8", "@types/cookie": "^0.4.0", "@types/js-cookie": "^2.2.6", + "@types/lodash.debounce": "^4.0.6", "@types/node": "^14.11.2", "@types/react": "^16.9.49", "graphql": "^15.3.0", diff --git a/yarn.lock b/yarn.lock index ea992a362..45e63e77e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1572,6 +1572,18 @@ dependencies: "@types/node" "*" +"@types/lodash.debounce@^4.0.6": + version "4.0.6" + resolved "https://registry.yarnpkg.com/@types/lodash.debounce/-/lodash.debounce-4.0.6.tgz#c5a2326cd3efc46566c47e4c0aa248dc0ee57d60" + integrity sha512-4WTmnnhCfDvvuLMaF3KV4Qfki93KebocUF45msxhYyjMttZDQYzHkO639ohhk8+oco2cluAFL3t5+Jn4mleylQ== + dependencies: + "@types/lodash" "*" + +"@types/lodash@*": + version "4.14.161" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.161.tgz#a21ca0777dabc6e4f44f3d07f37b765f54188b18" + integrity sha512-EP6O3Jkr7bXvZZSZYlsgt5DIjiGr0dXP1/jVEwVLTFgg0d+3lWVQkRavYVQszV7dYUwvg0B8R0MBDpcmXg7XIA== + "@types/node@*", "@types/node@^14.11.2": version "14.11.2" resolved "https://registry.yarnpkg.com/@types/node/-/node-14.11.2.tgz#2de1ed6670439387da1c9f549a2ade2b0a799256" @@ -4703,6 +4715,11 @@ lodash._reinterpolate@^3.0.0: resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0= +lodash.debounce@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" + integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168= + lodash.includes@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f"