Skip to content

Conversation

@acasazza
Copy link
Member

  • Create new core package
  • Add configuration and getPrices function
  • Add getPrices tests, biomejs, and global vitest config
  • Add new documentation folder
  • Add new getAccessToken function. Resolve Create getAccessToken function #617
  • Fix vite types env
  • Fix relative path for vitest
  • Remove ts comment
  • Add retrievePrice function. Resolve retrievePrice function #621
  • Add updatePrice function, and tests, remove package jwt-decode
  • Create new core package
  • Add configuration and getPrices function
  • Add getPrices tests, biomejs, and global vitest config
  • Add new documentation folder
  • Add new getAccessToken function. Resolve Create getAccessToken function #617
  • Fix vite types env
  • Fix relative path for vitest
  • Remove ts comment
  • Add retrievePrice function. Resolve retrievePrice function #621
  • Add updatePrice function, and tests, remove package jwt-decode
  • Add new hook usePrices and unit tests, update commercelayer/sdk to v7 into the core package
  • Expose Adyen dropin events (onReady, onSelect)
  • v4.21.0
  • Add stripe connected account
  • v4.22.0-beta.0
  • v4.22.0
  • Add new CKO flow library
  • v4.23.0
  • Fix load CKO configuration
  • v4.23.1
  • Fix CKO config types
  • v4.23.2
  • Fix show CKO payment error
  • v4.23.3-beta.0
  • Fix place draft order
  • v4.23.3-beta.1
  • v4.23.3
  • Fix avoid place order in draft
  • v4.23.4-beta.0
  • v4.23.4
  • Add Adyen subscriptions
  • Hide customer payments when the order contains a subscription
  • v4.24.0-beta.0
  • Reset Adyen stored payments when the order has a subscription
  • v4.24.0-beta.1
  • Add subscriptionPaymentMethods prop to filter available payment methods when there is a subscription order
  • v4.24.0-beta.2
  • Fix types
  • v4.24.0
  • Show all payment methods available
  • Fix avoid place order in draft, and express payments by CKO
  • v4.24.1-beta.0
  • Fix loading card info
  • v4.24.1-beta.1
  • Fix loading card info
  • v4.24.1-beta.2
  • Fix unused variable
  • v4.24.1-beta.3
  • Fix autoselect payment method with CKO. Resolve Autoselect not working with checkout.com #645
  • Update deps
  • Fix avoid place order in draft
  • v4.24.1-beta.4
  • Update biome configuration
  • v4.24.1
  • Add new prop showLoader in PaymentMethod component
  • v4.25.0-beta.0
  • Fix showLoader flow
  • v4.25.0-beta.1
  • Remove unused code
  • v4.25.0
  • Fix draft order and showLoader flow
  • v4.25.1-beta.0
  • v4.25.1
  • Fix post requests when a customer edit an address
  • v4.25.2-beta.0
  • Fix place order with declined transaction by CKO
  • v4.25.2-beta.1
  • Fix place order with declined transaction by CKO
  • v4.25.2-beta.2
  • Fix remove customer email
  • v4.25.2-beta.3
  • fix: update shipping, instead of billing address
  • v4.25.2-beta.4
  • fix: update address only if doesn't belong to a customer address
  • v4.25.2-beta.5
  • v4.25.2
  • fix: remove ref.current from dependencies array
  • v4.25.3-beta.0
  • v4.25.3
  • Fix place order flow with CKO using the auto-capture setting
  • v4.25.4-beta.0
  • v4.25.4
  • Fix duplicate addresses
  • v4.25.5-beta.0
  • v4.25.5
  • Fix duplicate save addresses
  • v4.25.6-beta.0
  • v4.25.6
  • Fix tax recalculations
  • v4.25.7-beta.0
  • Fix tax recalculations
  • v4.25.7-beta.1
  • Remove delete customer_email
  • v4.25.7-beta.2
  • Fix place order behaviour
  • v4.25.7-beta.3
  • Enable save address button even if billing info is required
  • v4.25.7-beta.4
  • v4.25.7
  • Update deps
  • Fix cko express payment
  • v4.25.8-beta.0
  • v4.25.8
  • feat: enable server side sorting for order list
  • v4.26.0
  • Fix rebuilt shipments
  • v4.26.1-beta.0
  • v4.26.1
  • feat: enable sorting on order subscriptions
  • v4.27.0
  • chore: avoid unused orders and subscriptions list requests on setup
  • chore: update default sort of customer payment sources
  • Fix save address input behaviour
  • v4.28.0
  • Fix ship to different address toggle
  • v4.28.1-beta.0
  • Fix ship to different address mode using customer address
  • v4.28.1-beta.1
  • Fix shipToDifferentAddress toggle
  • v4.28.1-beta.2
  • v4.28.1
  • Fix vulnerabilities
  • v4.28.2
  • fix: refresh order when billing or shipping address is updated
  • v4.28.3
  • Fix givex flow
  • Fix givex partially authorization flow
  • v4.28.3-beta.0
  • v4.28.4-beta.0
  • Fix payment flow using givex card
  • v4.28.4-beta.1
  • Fix show giftcard into the summary, remove manageAdyenGiftCard prop
  • v4.28.4
  • Unused variable
  • v4.28.4
  • Fix duplicate addresses when pay with Adyen
  • Fix show givex card details in the summary
  • Fix duplicate save address
  • Fix currencies format
  • v4.28.5-beta.0
  • v4.28.5
  • Fix place unpaid orders when payment source is declined
  • v4.28.6-beta.6
  • Fix place order
  • v4.28.6-beta.7
  • Fix place order button
  • v4.28.6-beta.8
  • v4.28.6
  • Create new core package
  • Add configuration and getPrices function
  • Add getPrices tests, biomejs, and global vitest config
  • Add new documentation folder
  • Add new getAccessToken function. Resolve Create getAccessToken function #617
  • Fix relative path for vitest
  • Add updatePrice function, and tests, remove package jwt-decode
  • Export PriceUpdate type
  • Update deps
  • Implement SWR into usePrices hook

@acasazza acasazza added the enhancement New feature or request label Jan 20, 2026
@acasazza acasazza self-assigned this Jan 20, 2026
@netlify
Copy link

netlify bot commented Jan 20, 2026

Deploy Preview for commercelayer-react-components failed.

Name Link
🔨 Latest commit 7cf0b6f
🔍 Latest deploy log https://app.netlify.com/projects/commercelayer-react-components/deploys/6970f504f276e7000888638a

@acasazza acasazza added this to the v5.0.0 milestone Jan 20, 2026
@acasazza acasazza linked an issue Jan 20, 2026 that may be closed by this pull request
@acasazza acasazza changed the base branch from main to v5.0.0 January 28, 2026 15:53
@acasazza acasazza requested a review from Copilot January 28, 2026 15:53
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements SWR (stale-while-revalidate) caching into the usePrices hook and adds support for retrieving and updating individual prices. The implementation also updates various dependencies across the monorepo packages.

Changes:

  • Integrated SWR library into the usePrices hook for data caching and revalidation
  • Added retrievePrice and updatePrice functions to the hook's API
  • Updated multiple package dependencies to their latest versions
  • Updated biome.json schema and formatting configuration

Reviewed changes

Copilot reviewed 11 out of 12 changed files in this pull request and generated 15 comments.

Show a summary per file
File Description
packages/hooks/src/prices/usePrices.ts Refactored to use SWR for caching, added retrieve and update operations
packages/hooks/src/prices/usePrices.test.ts Added tests for new retrieve and update functionality
packages/hooks/src/index.ts Created index file exporting usePrices hook
packages/hooks/package.json Added swr dependency and updated dev dependencies
packages/core/src/prices/index.ts Exported PriceUpdate type
packages/react-components/package.json Updated dependencies
packages/document/package.json Updated dependencies
packages/docs/package.json Updated dependencies
packages/core/package.json Updated dependencies
package.json Updated biome dependency
biome.json Updated schema version and reformatted

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 213 to 239
coreTest("should update a price", async ({ accessToken }) => {
const token = accessToken?.accessToken
const { result } = renderHook(() => usePrices(token))

// First fetch prices
act(() => {
result.current.fetchPrices()
})

await waitFor(() => {
expect(result.current.prices.length).toBeGreaterThan(0)
})

const priceToUpdate = result.current.prices[0]

// Update the price
await act(async () => {
await result.current.updatePrice({
id: priceToUpdate.id,
type: "prices",
})
})

await waitFor(() => {
expect(result.current.action).toBe("update")
})
})
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test only verifies that the action state is set to "update", but doesn't verify that the price was actually updated, that the update was reflected in the prices array, or that the return value from updatePrice contains the correct updated data. The test is incomplete and doesn't validate the core functionality.

The test should verify that the returned promise resolves to the updated price and that result.current.prices reflects the update.

Copilot uses AI. Check for mistakes.
Comment on lines +135 to +136
mutate(data, false)
}, [mutate, data])
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The clearError callback includes data in its dependency array, causing it to be recreated every time the data changes. This is a performance concern and can cause unnecessary re-renders in consuming components.

Consider using a ref to track the data or restructuring this callback to avoid the data dependency.

Suggested change
mutate(data, false)
}, [mutate, data])
mutate((currentData) => currentData, false)
}, [mutate])

Copilot uses AI. Check for mistakes.
Comment on lines +115 to 124
const handleUpdatePrice = useCallback(
async (resource: PriceUpdate): Promise<Price | undefined> => {
setPriceResource(resource)
setAction("update")
setShouldFetch(true)
await mutate()
return data?.find((p: Price) => p.id === resource.id)
},
[accessToken],
[mutate, data],
)
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The handleUpdatePrice function has the same race condition as handleRetrievePrice. It sets state variables and then immediately calls mutate() and returns data?.find(...). The state updates are asynchronous, so the SWR cache key may not have updated when mutate() is called, and the returned data may be stale.

Additionally, the function searches for the updated price in the current data array, but this search happens before the update has actually been fetched and applied to the SWR cache.

Copilot uses AI. Check for mistakes.
Comment on lines +82 to +85
return await getPrices({
accessToken,
params,
})
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The SWR fetcher function has a default case that duplicates the "get" case logic. This is redundant since the action state should always be one of the expected values ("get", "retrieve", "update", or null). If action is null, the SWR key would be null and the fetcher shouldn't be called.

The default case should either throw an error for unexpected action values or be removed entirely.

Suggested change
return await getPrices({
accessToken,
params,
})
throw new Error(`Unexpected action: ${String(action)}`)

Copilot uses AI. Check for mistakes.
Comment on lines +115 to 124
const handleUpdatePrice = useCallback(
async (resource: PriceUpdate): Promise<Price | undefined> => {
setPriceResource(resource)
setAction("update")
setShouldFetch(true)
await mutate()
return data?.find((p: Price) => p.id === resource.id)
},
[accessToken],
[mutate, data],
)
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The handleUpdatePrice callback includes data in its dependency array, which will cause the callback reference to change every time the data updates. This is unnecessary since the function only needs the current value of data when it executes, not when it's created.

This can lead to performance issues and unexpected behavior in components that use this callback as a dependency.

Copilot uses AI. Check for mistakes.
Comment on lines +94 to +101
const fetchPrices = useCallback(
(newParams?: Parameters<typeof getPrices>[0]["params"]) => {
setParams(newParams)
setShouldFetch(true)
setAction("get")
},
[],
)
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fetchPrices function always sets shouldFetch to true, but never sets it back to false after the fetch completes. This means once fetchPrices is called, shouldFetch remains true indefinitely, which could cause unexpected revalidations or side effects if the component re-renders and the SWR key changes.

Consider adding logic to reset shouldFetch after the fetch completes, or reconsider whether this state variable is necessary for the design.

Copilot uses AI. Check for mistakes.
Comment on lines +53 to +92
const { data, error, isLoading, isValidating, mutate } = useSWR<Price[]>(
shouldFetch && accessToken && action
? ["prices", action, accessToken, params, priceId, priceResource]
: null,
async (): Promise<Price[]> => {
switch (action) {
case "get":
return await getPrices({
accessToken,
params,
})
setState((prev: UsePricesState) => ({
...prev,
prices: prices,
}))
} catch (error: unknown) {
setState((prev: UsePricesState) => ({
...prev,
error:
error instanceof Error ? error.message : "Failed to fetch prices",
}))
case "retrieve": {
if (!priceId) throw new Error("Price ID is required for retrieve")
return [await retrievePrice({ accessToken, id: priceId })]
}
})
case "update": {
if (!priceResource)
throw new Error("Price resource is required for update")
const updatedPrice = await updatePrice({
accessToken,
resource: priceResource,
})
return data
? data.map((p: Price) =>
p.id === updatedPrice.id ? updatedPrice : p,
)
: [updatedPrice]
}
default:
return await getPrices({
accessToken,
params,
})
}
},
{
revalidateOnFocus: false,
revalidateOnReconnect: false,
},
)
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using a single SWR instance to handle three different operations (get list, retrieve single, update single) with different data structures is an anti-pattern. SWR is designed to cache and revalidate a specific resource at a specific URL/key.

The current implementation tries to use SWR for imperative operations (retrievePrice, updatePrice) which should happen on-demand, not as part of a cache/revalidation strategy. The retrieve and update operations should either:

  1. Be implemented without SWR (direct API calls)
  2. Use separate SWR hooks with appropriate keys
  3. Use SWR's mutation functionality (useSWRMutation) for updates

The current approach creates confusion about what data is cached and when, and makes the caching strategy unpredictable.

Copilot uses AI. Check for mistakes.
Comment on lines +103 to +113
const handleRetrievePrice = useCallback(
async (id: string): Promise<Price | undefined> => {
setPriceId(id)
setAction("retrieve")
setShouldFetch(true)
// Wait for SWR to fetch
await mutate()
return data?.[0]
},
[mutate, data],
)
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The handleRetrievePrice function has a race condition. It sets state variables (setPriceId, setAction, setShouldFetch) and then immediately calls mutate() and returns data?.[0]. However, the state updates are asynchronous and may not have taken effect when mutate() is called. Additionally, even after mutate() completes, the returned value data?.[0] may still be the old data from before the state changes triggered a new SWR fetch.

The function should wait for the SWR refetch to complete before returning the data. Consider using the return value from mutate() or implementing a different pattern to ensure the correct data is returned.

Copilot uses AI. Check for mistakes.
Comment on lines +53 to +56
const { data, error, isLoading, isValidating, mutate } = useSWR<Price[]>(
shouldFetch && accessToken && action
? ["prices", action, accessToken, params, priceId, priceResource]
: null,
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The SWR cache key includes all state variables (params, priceId, priceResource), which means every state change creates a new cache entry. This defeats the purpose of caching and can lead to memory leaks as the cache grows unbounded. For example, calling retrievePrice with different IDs will create separate cache entries for each ID, and the old entries won't be reused.

Consider using a simpler cache key (e.g., just the accessToken) and managing the data transformations separately, or use SWR's mutate function with optimistic updates.

Copilot uses AI. Check for mistakes.
Comment on lines +71 to +79
const updatedPrice = await updatePrice({
accessToken,
resource: priceResource,
})
return data
? data.map((p: Price) =>
p.id === updatedPrice.id ? updatedPrice : p,
)
: [updatedPrice]
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The update action attempts to merge the updated price with the existing data array, but this logic is flawed. The data variable referenced here is the previous SWR data state, not the result of the update operation. If data is undefined or doesn't contain the price being updated, this will return an array with just the updated price, potentially losing other prices that were previously loaded.

This also creates an inconsistent behavior where the update operation changes the entire prices array structure based on whether there was previous data or not.

Suggested change
const updatedPrice = await updatePrice({
accessToken,
resource: priceResource,
})
return data
? data.map((p: Price) =>
p.id === updatedPrice.id ? updatedPrice : p,
)
: [updatedPrice]
await updatePrice({
accessToken,
resource: priceResource,
})
// After updating a price, refetch the prices list to ensure
// the cached data remains consistent with the backend.
return await getPrices({
accessToken,
params,
})

Copilot uses AI. Check for mistakes.
@acasazza acasazza merged commit 0cec58a into v5.0.0 Jan 28, 2026
1 check failed
@acasazza acasazza deleted the v5/hooks/useSWR branch January 28, 2026 18:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement SWR in hook function Autoselect not working with checkout.com retrievePrice function Create getAccessToken function

2 participants