-
Couldn't load subscription status.
- Fork 39
demo(product-list): implement third-party data enrichment for Coveo products #6162
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| 'use client'; | ||
|
|
||
| import {Suspense, use} from 'react'; | ||
| import {Availability} from '@/lib/third-party-api-provider'; | ||
| import {useAvailability} from './providers/availability-provider'; | ||
|
|
||
| function SuspendedCustomAvailabilityBadge({productId}: {productId: string}) { | ||
| const {getAvailabilityPromise} = useAvailability(); | ||
| return ( | ||
| <Suspense fallback={<div>Loading...</div>}> | ||
| <CustomAvailabilityBadge | ||
| availabilityPromise={getAvailabilityPromise(productId)} | ||
| /> | ||
| </Suspense> | ||
| ); | ||
| } | ||
|
|
||
| const CustomAvailabilityBadge = ({ | ||
| availabilityPromise, | ||
| }: { | ||
| availabilityPromise: Promise<Availability> | undefined; | ||
| }) => { | ||
| const availability = availabilityPromise ? use(availabilityPromise) : null; | ||
| switch (availability) { | ||
| case Availability.High: | ||
| return <div>High Availability</div>; | ||
| case Availability.Medium: | ||
| return <div>Medium Availability</div>; | ||
| case Availability.Low: | ||
| return <div>Low Availability</div>; | ||
| case Availability.OutOfStock: | ||
| return <div>Out of stock</div>; | ||
| default: | ||
| return <div>Unknown Availability</div>; | ||
| } | ||
| }; | ||
|
|
||
| export {SuspendedCustomAvailabilityBadge as CustomAvailabilityBadge}; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| 'use client'; | ||
|
|
||
| import {createContext, type ReactNode, useContext} from 'react'; | ||
| import type {Availability} from '@/lib/third-party-api-provider'; | ||
|
|
||
| interface AvailabilityContextType { | ||
| getAvailabilityPromise: ( | ||
| productId: string | ||
| ) => Promise<Availability> | undefined; | ||
| } | ||
|
|
||
| const AvailabilityContext = createContext<AvailabilityContextType | undefined>( | ||
| undefined | ||
| ); | ||
|
|
||
| interface AvailabilityProviderProps { | ||
| children: ReactNode; | ||
| productIdToAvailabilityRequests: Map<string, Promise<Availability>>; | ||
| } | ||
|
|
||
| export function AvailabilityProvider({ | ||
| children, | ||
| productIdToAvailabilityRequests, | ||
| }: AvailabilityProviderProps) { | ||
| const getAvailabilityPromise = ( | ||
| productId: string | ||
| ): Promise<Availability> | undefined => { | ||
| return productIdToAvailabilityRequests.get(productId); | ||
| }; | ||
|
|
||
| return ( | ||
| <AvailabilityContext.Provider value={{getAvailabilityPromise}}> | ||
| {children} | ||
| </AvailabilityContext.Provider> | ||
| ); | ||
| } | ||
|
|
||
| export function useAvailability() { | ||
| const context = useContext(AvailabilityContext); | ||
| if (context === undefined) { | ||
| throw new Error( | ||
| 'useAvailability must be used within an AvailabilityProvider' | ||
| ); | ||
| } | ||
| return context; | ||
| } |
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd rename the file to "product-availability-api-provider" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| /** | ||
| * @file | ||
| * A fake 3rd party API that returns the availability of a product based on its id. | ||
| * | ||
| * The returned value of this fake API are random. | ||
| * The "response time" of the API is however rigged to illustrate various scenarios: | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Intuitively, I'd say that in a real-life scenario you'd probably be able to fetch this information in a single batch (i.e., get availability status for all products in the result list). Otherwise you'd really be hammering the external service with requests. With that in mind, we could still simulate latency, but it could just be:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Yeah we could do that. The goal with my current approach is that you can see suspense in action in one page load. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I understand, but I wonder if there's any use in demonstrating that unless we can think of a more realistic / common use case. It seems to me like an API that would accept only one product at a time would be incredibly inefficient. |
||
| * - The first and third request will respond 'fast' (<200ms) to represent the most optimistic cases | ||
| * - The fifth & sixth request will respond 'very slow' (>5s) to represent the worst case scenario case | ||
| * - The other request will respond between 500 & 1000ms. | ||
| * Every 10 requests, the counter of requests will reset. | ||
| */ | ||
|
|
||
| export enum Availability { | ||
| High = 'high', | ||
| Medium = 'medium', | ||
| Low = 'low', | ||
| OutOfStock = 'out-of-stock', | ||
| } | ||
|
Comment on lines
+13
to
+18
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Realistically, product availability would more likely be a discrete number of units, no? |
||
|
|
||
| const availabilities = Object.values(Availability); | ||
|
|
||
| let requestCounter = 0; | ||
|
|
||
| export const fetchAvailability = async ( | ||
| productId: string | ||
| ): Promise<Availability> => { | ||
| requestCounter++; | ||
| requestCounter %= 10; | ||
| const stableAvailability = | ||
| availabilities[Math.floor(Math.random() * availabilities.length)]; | ||
| let latency = Math.random() * 500 + 500; | ||
| if (requestCounter === 1 || requestCounter === 3) { | ||
| latency = Math.random() * 200; | ||
| } | ||
| if (requestCounter === 5 || requestCounter === 6) { | ||
| latency += 4.5e3; | ||
| } | ||
| return await new Promise((resolve) => { | ||
| // Simulate fast network latency | ||
| setTimeout(() => { | ||
| console.log( | ||
| `🔗 Fetched availability for product ${productId}: ${stableAvailability}` | ||
| ); | ||
| resolve(stableAvailability as Availability); | ||
| }, latency); | ||
| }); | ||
| }; | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AI alert for this; I used prop drillin' then asked Copilot to refactor this with a context.
If it smells, say so