Skip to content

Commit 7230c7d

Browse files
lkostrowskiclaude
andcommitted
lazy load product attributes per visible column using assignedAttribute(slug)
Replace bulk assignedAttributes fetch with per-attribute lazy loading: - New ProductListAttribute query uses assignedAttribute(slug:) to fetch one attribute at a time per visible column - Add slug field to GridAttributes selectedAttributes query - useProductListAttributes hook fires parallel queries per visible slug - Replaced useLazyQuery with useQuery/client.query for Apollo 3.4 compat - GridAttributes query now uses useQuery instead of useLazyQuery Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 831ef1b commit 7230c7d

10 files changed

Lines changed: 257 additions & 262 deletions

File tree

src/attributes/utils/assignedAttributes.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { type ProductListQuery } from "@dashboard/graphql";
1+
import { type ProductListAttributeQuery } from "@dashboard/graphql";
22
import { type RelayToFlat } from "@dashboard/types";
33

4-
type ProductListItems = NonNullable<RelayToFlat<ProductListQuery["products"]>>;
5-
type AssignedAttributeItem = ProductListItems[number]["assignedAttributes"][number];
4+
type ProductListAttributeItems = NonNullable<RelayToFlat<ProductListAttributeQuery["products"]>>;
5+
type AssignedAttributeItem = NonNullable<ProductListAttributeItems[number]["assignedAttribute"]>;
66

77
export function getDisplayValueFromAssignedAttribute(attr: AssignedAttributeItem): string {
88
switch (attr.__typename) {

src/graphql/hooks.generated.ts

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15939,9 +15939,6 @@ export const ProductListDocument = gql`
1593915939
updatedAt
1594015940
created
1594115941
description
15942-
assignedAttributes {
15943-
...ProductListAssignedAttribute
15944-
}
1594515942
}
1594615943
}
1594715944
pageInfo {
@@ -15953,8 +15950,7 @@ export const ProductListDocument = gql`
1595315950
totalCount
1595415951
}
1595515952
}
15956-
${ProductForListFragmentDoc}
15957-
${ProductListAssignedAttributeFragmentDoc}`;
15953+
${ProductForListFragmentDoc}`;
1595815954

1595915955
/**
1596015956
* __useProductListQuery__
@@ -16317,6 +16313,7 @@ export const GridAttributesDocument = gql`
1631716313
node {
1631816314
id
1631916315
name
16316+
slug
1632016317
}
1632116318
}
1632216319
}
@@ -16457,6 +16454,49 @@ export function useGridWarehousesLazyQuery(baseOptions?: ApolloReactHooks.LazyQu
1645716454
export type GridWarehousesQueryHookResult = ReturnType<typeof useGridWarehousesQuery>;
1645816455
export type GridWarehousesLazyQueryHookResult = ReturnType<typeof useGridWarehousesLazyQuery>;
1645916456
export type GridWarehousesQueryResult = Apollo.QueryResult<Types.GridWarehousesQuery, Types.GridWarehousesQueryVariables>;
16457+
export const ProductListAttributeDocument = gql`
16458+
query ProductListAttribute($ids: [ID!]!, $slug: String!) {
16459+
products(where: {ids: $ids}, first: 100) {
16460+
edges {
16461+
node {
16462+
id
16463+
assignedAttribute(slug: $slug) {
16464+
...ProductListAssignedAttribute
16465+
}
16466+
}
16467+
}
16468+
}
16469+
}
16470+
${ProductListAssignedAttributeFragmentDoc}`;
16471+
16472+
/**
16473+
* __useProductListAttributeQuery__
16474+
*
16475+
* To run a query within a React component, call `useProductListAttributeQuery` and pass it any options that fit your needs.
16476+
* When your component renders, `useProductListAttributeQuery` returns an object from Apollo Client that contains loading, error, and data properties
16477+
* you can use to render your UI.
16478+
*
16479+
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
16480+
*
16481+
* @example
16482+
* const { data, loading, error } = useProductListAttributeQuery({
16483+
* variables: {
16484+
* ids: // value for 'ids'
16485+
* slug: // value for 'slug'
16486+
* },
16487+
* });
16488+
*/
16489+
export function useProductListAttributeQuery(baseOptions: ApolloReactHooks.QueryHookOptions<Types.ProductListAttributeQuery, Types.ProductListAttributeQueryVariables>) {
16490+
const options = {...defaultOptions, ...baseOptions}
16491+
return ApolloReactHooks.useQuery<Types.ProductListAttributeQuery, Types.ProductListAttributeQueryVariables>(ProductListAttributeDocument, options);
16492+
}
16493+
export function useProductListAttributeLazyQuery(baseOptions?: ApolloReactHooks.LazyQueryHookOptions<Types.ProductListAttributeQuery, Types.ProductListAttributeQueryVariables>) {
16494+
const options = {...defaultOptions, ...baseOptions}
16495+
return ApolloReactHooks.useLazyQuery<Types.ProductListAttributeQuery, Types.ProductListAttributeQueryVariables>(ProductListAttributeDocument, options);
16496+
}
16497+
export type ProductListAttributeQueryHookResult = ReturnType<typeof useProductListAttributeQuery>;
16498+
export type ProductListAttributeLazyQueryHookResult = ReturnType<typeof useProductListAttributeLazyQuery>;
16499+
export type ProductListAttributeQueryResult = Apollo.QueryResult<Types.ProductListAttributeQuery, Types.ProductListAttributeQueryVariables>;
1646016500
export const ChannelDiagnosticsDocument = gql`
1646116501
query ChannelDiagnostics {
1646216502
channels {

src/graphql/types.generated.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12928,7 +12928,7 @@ export type ProductListQueryVariables = Exact<{
1292812928
}>;
1292912929

1293012930

12931-
export type ProductListQuery = { __typename: 'Query', products: { __typename: 'ProductCountableConnection', totalCount: number | null, edges: Array<{ __typename: 'ProductCountableEdge', node: { __typename: 'Product', updatedAt: any, created: any, description: any | null, id: string, name: string, assignedAttributes: Array<{ __typename: 'AssignedBooleanAttribute', booleanValue: boolean | null, attribute: { __typename: 'Attribute', id: string } } | { __typename: 'AssignedDateAttribute', dateValue: string | null, attribute: { __typename: 'Attribute', id: string } } | { __typename: 'AssignedDateTimeAttribute', dateTimeValue: any | null, attribute: { __typename: 'Attribute', id: string } } | { __typename: 'AssignedFileAttribute', attribute: { __typename: 'Attribute', id: string } } | { __typename: 'AssignedMultiCategoryReferenceAttribute', multiCategoryReferenceValue: Array<{ __typename: 'Category', name: string }>, attribute: { __typename: 'Attribute', id: string } } | { __typename: 'AssignedMultiChoiceAttribute', multiChoiceValue: Array<{ __typename: 'AssignedChoiceAttributeValue', name: string | null }>, attribute: { __typename: 'Attribute', id: string } } | { __typename: 'AssignedMultiCollectionReferenceAttribute', multiCollectionReferenceValue: Array<{ __typename: 'Collection', name: string }>, attribute: { __typename: 'Attribute', id: string } } | { __typename: 'AssignedMultiPageReferenceAttribute', multiPageReferenceValue: Array<{ __typename: 'Page', title: string }>, attribute: { __typename: 'Attribute', id: string } } | { __typename: 'AssignedMultiProductReferenceAttribute', multiProductReferenceValue: Array<{ __typename: 'Product', name: string }>, attribute: { __typename: 'Attribute', id: string } } | { __typename: 'AssignedMultiProductVariantReferenceAttribute', multiVariantReferenceValue: Array<{ __typename: 'ProductVariant', name: string }>, attribute: { __typename: 'Attribute', id: string } } | { __typename: 'AssignedNumericAttribute', numericValue: number | null, attribute: { __typename: 'Attribute', id: string } } | { __typename: 'AssignedPlainTextAttribute', plainTextValue: string | null, attribute: { __typename: 'Attribute', id: string } } | { __typename: 'AssignedSingleCategoryReferenceAttribute', categoryReferenceValue: { __typename: 'Category', name: string } | null, attribute: { __typename: 'Attribute', id: string } } | { __typename: 'AssignedSingleChoiceAttribute', singleChoiceValue: { __typename: 'AssignedChoiceAttributeValue', name: string | null } | null, attribute: { __typename: 'Attribute', id: string } } | { __typename: 'AssignedSingleCollectionReferenceAttribute', collectionReferenceValue: { __typename: 'Collection', name: string } | null, attribute: { __typename: 'Attribute', id: string } } | { __typename: 'AssignedSinglePageReferenceAttribute', pageReferenceValue: { __typename: 'Page', title: string } | null, attribute: { __typename: 'Attribute', id: string } } | { __typename: 'AssignedSingleProductReferenceAttribute', productReferenceValue: { __typename: 'Product', name: string } | null, attribute: { __typename: 'Attribute', id: string } } | { __typename: 'AssignedSingleProductVariantReferenceAttribute', variantReferenceValue: { __typename: 'ProductVariant', name: string } | null, attribute: { __typename: 'Attribute', id: string } } | { __typename: 'AssignedSwatchAttribute', swatchValue: { __typename: 'AssignedSwatchAttributeValue', name: string | null } | null, attribute: { __typename: 'Attribute', id: string } } | { __typename: 'AssignedTextAttribute', attribute: { __typename: 'Attribute', id: string } }>, thumbnail: { __typename: 'Image', url: string } | null, productType: { __typename: 'ProductType', id: string, name: string }, category?: { __typename: 'Category', id: string, name: string } | null, collections?: Array<{ __typename: 'Collection', id: string, name: string }> | null, channelListings: Array<{ __typename: 'ProductChannelListing', id: string, isPublished: boolean, publishedAt: any | null, pricing?: { __typename: 'ProductPricingInfo', priceRange: { __typename: 'TaxedMoneyRange', start: { __typename: 'TaxedMoney', net: { __typename: 'Money', amount: number, currency: string } } | null, stop: { __typename: 'TaxedMoney', net: { __typename: 'Money', amount: number, currency: string } } | null } | null } | null, channel: { __typename: 'Channel', id: string, name: string } }> | null } }>, pageInfo: { __typename: 'PageInfo', hasPreviousPage: boolean, hasNextPage: boolean, startCursor: string | null, endCursor: string | null } } | null };
12931+
export type ProductListQuery = { __typename: 'Query', products: { __typename: 'ProductCountableConnection', totalCount: number | null, edges: Array<{ __typename: 'ProductCountableEdge', node: { __typename: 'Product', updatedAt: any, created: any, description: any | null, id: string, name: string, thumbnail: { __typename: 'Image', url: string } | null, productType: { __typename: 'ProductType', id: string, name: string }, category?: { __typename: 'Category', id: string, name: string } | null, collections?: Array<{ __typename: 'Collection', id: string, name: string }> | null, channelListings: Array<{ __typename: 'ProductChannelListing', id: string, isPublished: boolean, publishedAt: any | null, pricing?: { __typename: 'ProductPricingInfo', priceRange: { __typename: 'TaxedMoneyRange', start: { __typename: 'TaxedMoney', net: { __typename: 'Money', amount: number, currency: string } } | null, stop: { __typename: 'TaxedMoney', net: { __typename: 'Money', amount: number, currency: string } } | null } | null } | null, channel: { __typename: 'Channel', id: string, name: string } }> | null } }>, pageInfo: { __typename: 'PageInfo', hasPreviousPage: boolean, hasNextPage: boolean, startCursor: string | null, endCursor: string | null } } | null };
1293212932

1293312933
export type ProductCountQueryVariables = Exact<{
1293412934
filter?: InputMaybe<ProductFilterInput>;
@@ -13000,7 +13000,7 @@ export type GridAttributesQueryVariables = Exact<{
1300013000
}>;
1300113001

1300213002

13003-
export type GridAttributesQuery = { __typename: 'Query', availableAttributes: { __typename: 'AttributeCountableConnection', edges: Array<{ __typename: 'AttributeCountableEdge', node: { __typename: 'Attribute', id: string, name: string | null } }>, pageInfo: { __typename: 'PageInfo', endCursor: string | null, hasNextPage: boolean, hasPreviousPage: boolean, startCursor: string | null } } | null, selectedAttributes: { __typename: 'AttributeCountableConnection', edges: Array<{ __typename: 'AttributeCountableEdge', node: { __typename: 'Attribute', id: string, name: string | null } }> } | null };
13003+
export type GridAttributesQuery = { __typename: 'Query', availableAttributes: { __typename: 'AttributeCountableConnection', edges: Array<{ __typename: 'AttributeCountableEdge', node: { __typename: 'Attribute', id: string, name: string | null } }>, pageInfo: { __typename: 'PageInfo', endCursor: string | null, hasNextPage: boolean, hasPreviousPage: boolean, startCursor: string | null } } | null, selectedAttributes: { __typename: 'AttributeCountableConnection', edges: Array<{ __typename: 'AttributeCountableEdge', node: { __typename: 'Attribute', id: string, name: string | null, slug: string | null } }> } | null };
1300413004

1300513005
export type AvailableColumnAttributesQueryVariables = Exact<{
1300613006
search: Scalars['String'];
@@ -13022,6 +13022,14 @@ export type GridWarehousesQueryVariables = Exact<{
1302213022

1302313023
export type GridWarehousesQuery = { __typename: 'Query', availableWarehouses: { __typename: 'WarehouseCountableConnection', edges: Array<{ __typename: 'WarehouseCountableEdge', node: { __typename: 'Warehouse', id: string, name: string } }>, pageInfo: { __typename: 'PageInfo', endCursor: string | null, hasNextPage: boolean, hasPreviousPage: boolean, startCursor: string | null } } | null, selectedWarehouses: { __typename: 'WarehouseCountableConnection', edges: Array<{ __typename: 'WarehouseCountableEdge', node: { __typename: 'Warehouse', id: string, name: string } }> } | null };
1302413024

13025+
export type ProductListAttributeQueryVariables = Exact<{
13026+
ids: Array<Scalars['ID']> | Scalars['ID'];
13027+
slug: Scalars['String'];
13028+
}>;
13029+
13030+
13031+
export type ProductListAttributeQuery = { __typename: 'Query', products: { __typename: 'ProductCountableConnection', edges: Array<{ __typename: 'ProductCountableEdge', node: { __typename: 'Product', id: string, assignedAttribute: { __typename: 'AssignedBooleanAttribute', booleanValue: boolean | null, attribute: { __typename: 'Attribute', id: string } } | { __typename: 'AssignedDateAttribute', dateValue: string | null, attribute: { __typename: 'Attribute', id: string } } | { __typename: 'AssignedDateTimeAttribute', dateTimeValue: any | null, attribute: { __typename: 'Attribute', id: string } } | { __typename: 'AssignedFileAttribute', attribute: { __typename: 'Attribute', id: string } } | { __typename: 'AssignedMultiCategoryReferenceAttribute', multiCategoryReferenceValue: Array<{ __typename: 'Category', name: string }>, attribute: { __typename: 'Attribute', id: string } } | { __typename: 'AssignedMultiChoiceAttribute', multiChoiceValue: Array<{ __typename: 'AssignedChoiceAttributeValue', name: string | null }>, attribute: { __typename: 'Attribute', id: string } } | { __typename: 'AssignedMultiCollectionReferenceAttribute', multiCollectionReferenceValue: Array<{ __typename: 'Collection', name: string }>, attribute: { __typename: 'Attribute', id: string } } | { __typename: 'AssignedMultiPageReferenceAttribute', multiPageReferenceValue: Array<{ __typename: 'Page', title: string }>, attribute: { __typename: 'Attribute', id: string } } | { __typename: 'AssignedMultiProductReferenceAttribute', multiProductReferenceValue: Array<{ __typename: 'Product', name: string }>, attribute: { __typename: 'Attribute', id: string } } | { __typename: 'AssignedMultiProductVariantReferenceAttribute', multiVariantReferenceValue: Array<{ __typename: 'ProductVariant', name: string }>, attribute: { __typename: 'Attribute', id: string } } | { __typename: 'AssignedNumericAttribute', numericValue: number | null, attribute: { __typename: 'Attribute', id: string } } | { __typename: 'AssignedPlainTextAttribute', plainTextValue: string | null, attribute: { __typename: 'Attribute', id: string } } | { __typename: 'AssignedSingleCategoryReferenceAttribute', categoryReferenceValue: { __typename: 'Category', name: string } | null, attribute: { __typename: 'Attribute', id: string } } | { __typename: 'AssignedSingleChoiceAttribute', singleChoiceValue: { __typename: 'AssignedChoiceAttributeValue', name: string | null } | null, attribute: { __typename: 'Attribute', id: string } } | { __typename: 'AssignedSingleCollectionReferenceAttribute', collectionReferenceValue: { __typename: 'Collection', name: string } | null, attribute: { __typename: 'Attribute', id: string } } | { __typename: 'AssignedSinglePageReferenceAttribute', pageReferenceValue: { __typename: 'Page', title: string } | null, attribute: { __typename: 'Attribute', id: string } } | { __typename: 'AssignedSingleProductReferenceAttribute', productReferenceValue: { __typename: 'Product', name: string } | null, attribute: { __typename: 'Attribute', id: string } } | { __typename: 'AssignedSingleProductVariantReferenceAttribute', variantReferenceValue: { __typename: 'ProductVariant', name: string } | null, attribute: { __typename: 'Attribute', id: string } } | { __typename: 'AssignedSwatchAttribute', swatchValue: { __typename: 'AssignedSwatchAttributeValue', name: string | null } | null, attribute: { __typename: 'Attribute', id: string } } | { __typename: 'AssignedTextAttribute', attribute: { __typename: 'Attribute', id: string } } | null } }> } | null };
13032+
1302513033
export type ChannelDiagnosticsQueryVariables = Exact<{ [key: string]: never; }>;
1302613034

1302713035

src/products/components/ProductListDatagrid/ProductListDatagrid.tsx

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// @ts-strict-ignore
2-
import { type LazyQueryResult } from "@apollo/client/react";
2+
import { type QueryResult } from "@apollo/client/react";
33
import { ColumnPicker } from "@dashboard/components/Datagrid/ColumnPicker/ColumnPicker";
44
import { useColumns } from "@dashboard/components/Datagrid/ColumnPicker/useColumns";
55
import { Datagrid } from "@dashboard/components/Datagrid/Datagrid";
@@ -49,14 +49,15 @@ import {
4949
} from "./datagrid";
5050
import { messages } from "./messages";
5151
import { usePriceClick } from "./usePriceClick";
52+
import { useProductListAttributes } from "./useProductListAttributes";
5253

5354
interface ProductListDatagridProps
5455
extends ListProps<ProductListColumns>,
5556
PageListProps<ProductListColumns>,
5657
SortPage<ProductListUrlSortField>,
5758
ChannelProps {
5859
activeAttributeSortId: string;
59-
gridAttributesOpts: LazyQueryResult<
60+
gridAttributesOpts: QueryResult<
6061
GridAttributesQuery,
6162
Exact<{
6263
ids: string | string[];
@@ -98,6 +99,32 @@ export const ProductListDatagrid = ({
9899
const productsLength = getProductRowsLength(disabled, products, disabled);
99100
const onPriceClick = usePriceClick({ isChannelSelected });
100101

102+
const productIds = useMemo(() => products?.map(p => p.id) ?? [], [products]);
103+
104+
const visibleAttributeSlugs = useMemo(() => {
105+
const selectedAttributes = mapEdgesToItems(gridAttributesOpts.data?.selectedAttributes);
106+
const visibleAttrIds = (settings?.columns ?? [])
107+
.filter(isAttributeColumnValue)
108+
.map(getAttributeIdFromColumnValue);
109+
110+
if (visibleAttrIds.length === 0 || !selectedAttributes) {
111+
return [];
112+
}
113+
114+
return visibleAttrIds
115+
.map(id => {
116+
const attr = selectedAttributes.find(a => a.id === id);
117+
118+
return attr?.slug ? { id, slug: attr.slug } : null;
119+
})
120+
.filter(Boolean) as Array<{ id: string; slug: string }>;
121+
}, [settings?.columns, gridAttributesOpts.data]);
122+
123+
const { getAssignedAttribute, attributesLoading } = useProductListAttributes({
124+
productIds,
125+
visibleAttributeSlugs,
126+
});
127+
101128
const handleColumnChange = useCallback(
102129
(picked: ProductListColumns[]) => {
103130
onUpdateListSettings("columns", picked.filter(Boolean));
@@ -247,8 +274,18 @@ export const ProductListDatagrid = ({
247274
theme,
248275
locale,
249276
selectedChannelId,
277+
getAssignedAttribute,
278+
attributesLoading,
250279
}),
251-
[visibleColumns, products, intl, locale, selectedChannelId],
280+
[
281+
visibleColumns,
282+
products,
283+
intl,
284+
locale,
285+
selectedChannelId,
286+
getAssignedAttribute,
287+
attributesLoading,
288+
],
252289
);
253290

254291
return (

0 commit comments

Comments
 (0)