Skip to content

perf(fetchProduct): batch multiple product requests into single GraphQL call#726

Open
Copilot wants to merge 7 commits intomainfrom
copilot/clone-pr-665
Open

perf(fetchProduct): batch multiple product requests into single GraphQL call#726
Copilot wants to merge 7 commits intomainfrom
copilot/clone-pr-665

Conversation

Copy link
Contributor

Copilot AI commented Nov 20, 2025

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:

  • Collects product handles within same frame
  • Deduplicates identical handles
  • Integrates transparently with existing cache

Smart query selection:

  • Single handle → ProductByHandle query
  • Multiple handles → ProductsByHandles query with phrase queries for exact matching

Error handling:

  • Partial failures supported (individual products can fail without blocking others)
  • Network errors reject all pending requests

Changes

New batch query (getProductsByHandles.graphql):

query ProductsByHandles($query: String!, $first: Int!) {
  products(first: $first, query: $query) {
    nodes { id, handle, title, ... }
  }
}

Updated fetchProduct.ts:

// Before: Direct fetch
export const [fetchProduct, clearProductCache] = cached(async (handle: string) => {
  const response = await fetch(...)
  return flattenResponse(responseData)
})

// After: Batched with RAF
function requestProduct(handle: string): Promise<ShopifyProduct> {
  return new Promise((resolve, reject) => {
    state.pendingRequests.get(handle).push({ handle, resolve, reject })
    if (!state.scheduledFlush) {
      state.scheduledFlush = requestAnimationFrame(() => flush())
    }
  })
}

Query optimization:

  • Uses phrase queries with quoted handles: handle:"product-name" instead of handle:product-name
  • Ensures exact matching for handles with special characters (hyphens, spaces, etc.)
  • Query example: handle:"product-1" OR handle:"product-2" OR handle:"product-3"

Test helper (test/helpers/productHandlers.ts):

  • Simplified API: accepts product array instead of response objects
  • Supports both single and batch query patterns
  • Updated regex patterns to handle quoted phrase query syntax

Mock products (src/mock/products.ts):

  • Added centralized mock products: mockFetchProduct1, mockFetchProduct2, mockFetchProduct3
  • Follows testing conventions for reusable mock data across test files

Code quality improvements:

  • Removed 13 unnecessary inline comments that restated code logic (per coding guidelines)
  • Improved type safety with proper ShopifyProduct & { handle: string } annotation for productNode
  • Removed redundant type aliases and unnecessary type casts

Testing

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

This section details on the original issue you should resolve

<issue_title>clone PR</issue_title>
<issue_description>create a new clean version of this PR #665</issue_description>

Comments on the Issue (you are @copilot in this section)


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

…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>
Copilot AI changed the title [WIP] Create a new clean version of PR 665 perf(fetchProduct): batch multiple product requests into single GraphQL call Nov 20, 2025
Copilot AI requested a review from timowestnosto November 20, 2025 17:00
@timowestnosto timowestnosto marked this pull request as ready for review November 20, 2025 17:46
Copilot AI review requested due to automatic review settings November 20, 2025 17:46
Copy link
Contributor

@timowestnosto timowestnosto left a comment

Choose a reason for hiding this comment

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

@copilot fix these

Copy link
Contributor

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 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 ProductsByHandles GraphQL 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

variables: { query?: string; handle?: string; first?: number; language?: string; country?: string }
}

// Check if this is a single product query
Copy link

Copilot AI Nov 20, 2025

Choose a reason for hiding this comment

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

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.

Copilot generated this review using guidance from repository custom instructions.
Copy link
Contributor

Choose a reason for hiding this comment

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

@copilot apply changes based on this feedback

}

async function fetchBatch(handles: string[], requestsMap: Map<string, PendingRequest[]>) {
// Build query string to match products by handle
Copy link

Copilot AI Nov 20, 2025

Choose a reason for hiding this comment

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

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.

Copilot generated this review using guidance from repository custom instructions.
Copy link
Contributor

Choose a reason for hiding this comment

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

@copilot apply changes based on this feedback


const responseData = await response.json()

// Create a map of handle to product
Copy link

Copilot AI Nov 20, 2025

Choose a reason for hiding this comment

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

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.

Copilot generated this review using guidance from repository custom instructions.
Copy link
Contributor

Choose a reason for hiding this comment

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

@copilot apply changes based on this feedback

})
}

// Resolve or reject each request
Copy link

Copilot AI Nov 20, 2025

Choose a reason for hiding this comment

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

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.

Copilot generated this review using guidance from repository custom instructions.
Copy link
Contributor

Choose a reason for hiding this comment

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

@copilot apply changes based on this feedback

await fetchBatch(handles, requestsMap)
}
} catch (error) {
// Reject all pending requests with the error
Copy link

Copilot AI Nov 20, 2025

Choose a reason for hiding this comment

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

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.

Copilot generated this review using guidance from repository custom instructions.
Copy link
Contributor

Choose a reason for hiding this comment

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

@copilot apply changes based on this feedback

})
}

// Check if this is a batch query with products query
Copy link

Copilot AI Nov 20, 2025

Choose a reason for hiding this comment

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

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.

Copilot generated this review using guidance from repository custom instructions.
Copy link
Contributor

Choose a reason for hiding this comment

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

@copilot apply changes based on this feedback


// 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
Copy link

Copilot AI Nov 20, 2025

Choose a reason for hiding this comment

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

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.

Copilot generated this review using guidance from repository custom instructions.
Copy link
Contributor

Choose a reason for hiding this comment

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

@copilot apply changes based on this feedback


function requestProduct(handle: string): Promise<ShopifyProduct> {
return new Promise((resolve, reject) => {
// Get or create array for this handle
Copy link

Copilot AI Nov 20, 2025

Choose a reason for hiding this comment

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

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.

Copilot generated this review using guidance from repository custom instructions.
Copy link
Contributor

Choose a reason for hiding this comment

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

@copilot apply changes based on this feedback

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>
@timowestnosto
Copy link
Contributor

@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>
Copy link
Contributor Author

Copilot AI commented Nov 20, 2025

@copilot fix build and lint errors

No build or lint errors found. All validations passing:

  • ✅ Lint: No errors
  • ✅ TypeCheck: No errors
  • ✅ Tests: All 376 tests passing

Copy link
Contributor

@timowestnosto timowestnosto left a comment

Choose a reason for hiding this comment

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

@copilot fix this

}

async function fetchBatch(handles: string[], requestsMap: Map<string, PendingRequest[]>) {
const queryString = handles.map(h => `handle:${h}`).join(" OR ")
Copy link
Contributor

Choose a reason for hiding this comment

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

use phrase queries here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

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>
Copy link
Contributor Author

Copilot AI commented Nov 20, 2025

@copilot fix this

Fixed. Updated query to use phrase queries for handle search. (5f4fdd0)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

clone PR

2 participants