Memoize functions in commerce hooks and debounce update

This commit is contained in:
Luis Alvarez
2020-10-08 20:36:31 -05:00
parent 0aac955910
commit 5c4f3e7ff2
10 changed files with 95 additions and 49 deletions

View File

@@ -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<Cart>, { item }: AddItemBody) {
export default function useAddItem() {
const { mutate } = useCart()
const fn = useCartAddItem<Cart, AddItemBody>(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]
)
}

View File

@@ -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<Cart | null, RemoveItemBody>(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]
)
}

View File

@@ -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<Cart | null, UpdateItemBody>(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]
)
}

View File

@@ -19,10 +19,9 @@ const CartProvider: FC<CartProviderProps> = ({ children, query, url }) => {
}
function useCart<C>() {
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,

View File

@@ -1,11 +1,15 @@
import { useCallback } from 'react'
import { Fetcher, useCommerce } from '..'
export default function useAddItem<T, Input>(
fetcher: (fetch: Fetcher<T>, input: Input) => T | Promise<T>
) {
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]
)
}

View File

@@ -1,11 +1,15 @@
import { useCallback } from 'react'
import { Fetcher, useCommerce } from '..'
export default function useRemoveItem<T, Input>(
fetcher: (fetch: Fetcher<T>, input: Input) => T | Promise<T>
) {
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]
)
}

View File

@@ -1,11 +1,15 @@
import { useCallback } from 'react'
import { Fetcher, useCommerce } from '..'
export default function useUpdateItem<T, Input>(
fetcher: (fetch: Fetcher<T>, input: Input) => T | Promise<T>
) {
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]
)
}

View File

@@ -1,14 +1,21 @@
import { createContext, ReactNode, useContext, useMemo } from 'react'
import {
ReactNode,
MutableRefObject,
createContext,
useContext,
useMemo,
useRef,
} from 'react'
const Commerce = createContext<CommerceConfig | null>(null)
export type CommerceProps = {
children?: ReactNode
config: CommerceConfig
config: { fetcher: Fetcher<any> } & CommerceConfig
}
export type CommerceConfig = {
fetcher: Fetcher<any>
fetcherRef: MutableRefObject<any>
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 <Commerce.Provider value={cfg}>{children}</Commerce.Provider>