perf(fetchProduct): batch multiple product requests into single GraphQL call#726
perf(fetchProduct): batch multiple product requests into single GraphQL call#726
Conversation
…tion - Add batching mechanism using requestAnimationFrame to collect product requests - Create new GraphQL query `getProductsByHandles.graphql` for batch fetching - Update `getProductByHandle.graphql` with additional fields (handle, image metadata) - Implement smart query selection (single vs batch) - Add comprehensive test suite with 10 new tests for batching functionality - Create reusable test helper `test/helpers/productHandlers.ts` - Remove unused `getProductsByIds.graphql` file - All 376 tests passing with full lint and typecheck compliance Co-authored-by: timowestnosto <13622115+timowestnosto@users.noreply.github.com>
timowestnosto
left a comment
There was a problem hiding this comment.
@copilot fix these
There was a problem hiding this comment.
Pull Request Overview
This PR implements request batching for product fetches to reduce network overhead when multiple components request products simultaneously. Using requestAnimationFrame, the implementation collects product handles within the same frame, deduplicates them, and dispatches either a single-product query or a batch query depending on the number of handles.
- Introduces RAF-based batching layer that transparently integrates with existing cache
- Adds new
ProductsByHandlesGraphQL query for multi-product fetching - Provides comprehensive test coverage (10 new tests) for batching, deduplication, caching, and error handling
Reviewed Changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 17 comments.
Show a summary per file
| File | Description |
|---|---|
| src/shopify/graphql/fetchProduct.ts | Core batching implementation with requestProduct, flush, fetchSingle, and fetchBatch functions |
| src/shopify/graphql/getProductsByHandles.graphql | New GraphQL query for fetching multiple products using handle filters |
| src/shopify/graphql/getProductByHandle.graphql | Updated to include handle field and additional image metadata (altText, thumbhash) |
| src/shopify/graphql/getProductsByIds.graphql | Removed unused query (likely replaced by the new handles-based approach) |
| test/shopify/fetchProduct.spec.tsx | Comprehensive test suite covering batching, caching, error handling, and edge cases |
| test/helpers/productHandlers.ts | New test helper for mocking product GraphQL endpoints in both single and batch modes |
test/helpers/productHandlers.ts
Outdated
| variables: { query?: string; handle?: string; first?: number; language?: string; country?: string } | ||
| } | ||
|
|
||
| // Check if this is a single product query |
There was a problem hiding this comment.
The inline comment "Check if this is a single product query" restates what the code clearly does. According to the project's coding guidelines (guideline 1000002), comments should only be added when necessary to explain complex changes or when matching existing comment style. This comment can be removed.
src/shopify/graphql/fetchProduct.ts
Outdated
| } | ||
|
|
||
| async function fetchBatch(handles: string[], requestsMap: Map<string, PendingRequest[]>) { | ||
| // Build query string to match products by handle |
There was a problem hiding this comment.
The inline comment "Build query string to match products by handle" restates what the code clearly does. According to the project's coding guidelines (guideline 1000002), comments should only be added when necessary to explain complex changes or when matching existing comment style. This comment can be removed.
src/shopify/graphql/fetchProduct.ts
Outdated
|
|
||
| const responseData = await response.json() | ||
|
|
||
| // Create a map of handle to product |
There was a problem hiding this comment.
The inline comment "Create a map of handle to product" restates what the code clearly does. According to the project's coding guidelines (guideline 1000002), comments should only be added when necessary to explain complex changes or when matching existing comment style. This comment can be removed.
src/shopify/graphql/fetchProduct.ts
Outdated
| }) | ||
| } | ||
|
|
||
| // Resolve or reject each request |
There was a problem hiding this comment.
The inline comment "Resolve or reject each request" restates what the code clearly does. According to the project's coding guidelines (guideline 1000002), comments should only be added when necessary to explain complex changes or when matching existing comment style. This comment can be removed.
src/shopify/graphql/fetchProduct.ts
Outdated
| await fetchBatch(handles, requestsMap) | ||
| } | ||
| } catch (error) { | ||
| // Reject all pending requests with the error |
There was a problem hiding this comment.
The inline comment "Reject all pending requests with the error" restates what the code clearly does. According to the project's coding guidelines (guideline 1000002), comments should only be added when necessary to explain complex changes or when matching existing comment style. This comment can be removed.
test/helpers/productHandlers.ts
Outdated
| }) | ||
| } | ||
|
|
||
| // Check if this is a batch query with products query |
There was a problem hiding this comment.
The inline comment "Check if this is a batch query with products query" restates what the code clearly does. According to the project's coding guidelines (guideline 1000002), comments should only be added when necessary to explain complex changes or when matching existing comment style. This comment can be removed.
test/shopify/fetchProduct.spec.tsx
Outdated
|
|
||
| // Check if this is a batch query with products query | ||
| if (body.query.includes("ProductsByHandles") && body.variables.query) { | ||
| // Parse the query string to extract handles |
There was a problem hiding this comment.
The inline comment "Parse the query string to extract handles" restates what the code clearly does. According to the project's coding guidelines (guideline 1000002), comments should only be added when necessary to explain complex changes or when matching existing comment style. This comment can be removed.
src/shopify/graphql/fetchProduct.ts
Outdated
|
|
||
| function requestProduct(handle: string): Promise<ShopifyProduct> { | ||
| return new Promise((resolve, reject) => { | ||
| // Get or create array for this handle |
There was a problem hiding this comment.
The inline comment "Get or create array for this handle" restates what the code clearly does. According to the project's coding guidelines (guideline 1000002), comments should only be added when necessary to explain complex changes or when matching existing comment style. This comment can be removed.
Move mockProduct1, mockProduct2, and mockProduct3 from fetchProduct.spec.tsx to src/mock/products.ts as mockFetchProduct1, mockFetchProduct2, and mockFetchProduct3. This follows the testing conventions documented in testing.instructions.md. Co-authored-by: timowestnosto <13622115+timowestnosto@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
|
@copilot fix build and lint errors |
- Remove inline comments that restate code logic per coding guidelines - Improve type annotation for productNode to include handle field - All tests passing with improved code clarity Co-authored-by: timowestnosto <13622115+timowestnosto@users.noreply.github.com>
No build or lint errors found. All validations passing:
|
src/shopify/graphql/fetchProduct.ts
Outdated
| } | ||
|
|
||
| async function fetchBatch(handles: string[], requestsMap: Map<string, PendingRequest[]>) { | ||
| const queryString = handles.map(h => `handle:${h}`).join(" OR ") |
There was a problem hiding this comment.
use phrase queries here
There was a problem hiding this comment.
Updated to use phrase queries with quoted handles: handle:"${h}". This ensures exact matching for handles with special characters. Updated test regex patterns to match the new syntax. (5f4fdd0)
Wrap handles in quotes to create phrase queries (handle:"product-name") instead of token queries (handle:product-name). This ensures exact matching for handles with special characters like hyphens, spaces, etc. Updated regex patterns in test helpers to match quoted handle syntax. Co-authored-by: timowestnosto <13622115+timowestnosto@users.noreply.github.com>
Context
Multiple components fetching products simultaneously (e.g., 12 SimpleCard instances in a grid) create separate GraphQL requests. This PR batches requests within the same animation frame into a single call, reducing network overhead by ~92%.
Related Jira ticket
N/A
Screenshots
N/A
Implementation
Batching layer using
requestAnimationFrame:Smart query selection:
ProductByHandlequeryProductsByHandlesquery with phrase queries for exact matchingError handling:
Changes
New batch query (
getProductsByHandles.graphql):Updated fetchProduct.ts:
Query optimization:
handle:"product-name"instead ofhandle:product-namehandle:"product-1" OR handle:"product-2" OR handle:"product-3"Test helper (
test/helpers/productHandlers.ts):Mock products (
src/mock/products.ts):mockFetchProduct1,mockFetchProduct2,mockFetchProduct3Code quality improvements:
ShopifyProduct & { handle: string }annotation for productNodeTesting
10 new tests covering batching, deduplication, cache integration, error handling. All 376 tests passing.
Performance
Grid of 12 products: 12 requests → 1 request (92% reduction)
Original prompt
✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.