Skip to content

Commit

Permalink
Optimize Calls to Product Endpoint By Allowing Slugs to be Specified …
Browse files Browse the repository at this point in the history
…to Avoid Loading all Products (#88942)

* Allow individual requests to be sent by adding product slug as prop for QueryProductsList and ProductsList.useProducts
  • Loading branch information
briowill authored Apr 9, 2024
1 parent 18026ac commit b5eef14
Show file tree
Hide file tree
Showing 11 changed files with 48 additions and 30 deletions.
11 changes: 6 additions & 5 deletions client/components/data/query-products-list/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ const request =
dispatch( requestProductsList( props ) );
};

export function useQueryProductsList( { type = 'all', currency, persist } = {} ) {
export function useQueryProductsList( { type = 'all', currency, persist, productSlugList } = {} ) {
const dispatch = useDispatch();

// Only runs on mount.
useEffect( () => {
dispatch( request( { type, currency, persist } ) );
}, [ dispatch, type, persist, currency ] );
dispatch( request( { type, currency, persist, product_slugs: productSlugList } ) );
}, [ dispatch, type, persist, currency, productSlugList ] );

return null;
}
Expand All @@ -34,8 +34,9 @@ export function useQueryProductsList( { type = 'all', currency, persist } = {} )
* "jetpack" for Jetpack products only, or undefined for all products.
* @param {string} [props.currency] The currency code to override the currency used on the account.
* @param {boolean} [props.persist] Set to true to persist the products list in the store.
* @param {string[]} [props.productSlugList] Indicates the specific products being requested. Optional.
* @returns {null} No visible output.
*/
export default function QueryProductsList( { type = 'all', currency, persist } ) {
return useQueryProductsList( { type, currency, persist } );
export default function QueryProductsList( { type = 'all', currency, persist, productSlugList } ) {
return useQueryProductsList( { type, currency, persist, productSlugList } );
}
2 changes: 0 additions & 2 deletions client/my-sites/add-ons/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { css, Global } from '@emotion/react';
import styled from '@emotion/styled';
import { useTranslate } from 'i18n-calypso';
import DocumentHead from 'calypso/components/data/document-head';
import QueryProductsList from 'calypso/components/data/query-products-list';
import QuerySitePurchases from 'calypso/components/data/query-site-purchases';
import EmptyContent from 'calypso/components/empty-content';
import Main from 'calypso/components/main';
Expand Down Expand Up @@ -119,7 +118,6 @@ const AddOnsMain = () => {
return (
<div>
<Global styles={ globalOverrides } />
<QueryProductsList />
<QuerySitePurchases siteId={ selectedSite?.ID } />
<PageViewTracker path="/add-ons/:site" title="Add-Ons" />
<ContentWithHeader>
Expand Down
5 changes: 4 additions & 1 deletion client/my-sites/marketing/do-it-for-me/difm-landing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,7 @@ export default function DIFMLanding( {
siteId?: number | null;
isStoreFlow: boolean;
} ) {
const requiredProductSlugs = [ PLAN_PREMIUM, WPCOM_DIFM_LITE, PLAN_BUSINESS ];
const translate = useTranslate();

const product = useSelector( ( state ) => getProductBySlug( state, WPCOM_DIFM_LITE ) );
Expand Down Expand Up @@ -396,7 +397,9 @@ export default function DIFMLanding( {

return (
<>
{ ! hasPriceDataLoaded && <QueryProductsList /> }
{ ! hasPriceDataLoaded && (
<QueryProductsList productSlugList={ requiredProductSlugs } type="partial" />
) }
<Wrapper>
<ContentSection>
{ /* @ts-expect-error FormattedHeader is not typed and it's causing issues with the styled component */ }
Expand Down
6 changes: 5 additions & 1 deletion client/signup/steps/page-picker/use-cart-for-difm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,11 @@ export function useCartForDIFM( {
// [Effect] Loads required initial data
useEffect( () => {
if ( ! difmLiteProduct || ! currencyCode ) {
dispatch( requestProductsList() );
const query = {
type: 'partial',
product_slugs: [ WPCOM_DIFM_LITE, PLAN_BUSINESS, PLAN_PREMIUM ],
};
dispatch( requestProductsList( query ) );
}
}, [ dispatch, difmLiteProduct, currencyCode ] );

Expand Down
9 changes: 8 additions & 1 deletion client/state/products-list/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,22 @@ export function receiveProductsList( productsList, type = null ) {
* Requests the list of all products from the WPCOM API.
* @param {Object} [query] A list of request parameters.
* @param {string} query.type The type of products to request (e.g., "jetpack");
* @param {string[]} [query.product_slugs] The specific products being requested. Optional.
*
* or undefined, for all products
* @returns {Function} an Action thunk
*/
export function requestProductsList( query = {} ) {
const requestQuery = { ...query };
if ( query.product_slugs && query.product_slugs.length > 0 ) {
const product_slugs = query.product_slugs?.join( ',' );
requestQuery.product_slugs = product_slugs;
}
return ( dispatch ) => {
dispatch( { type: PRODUCTS_LIST_REQUEST } );

return wpcom.req
.get( '/products', query )
.get( '/products', requestQuery )
.then( ( productsList ) => dispatch( receiveProductsList( productsList, query.type ) ) )
.catch( ( error ) =>
dispatch( {
Expand Down
2 changes: 1 addition & 1 deletion client/state/products-list/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { productsListSchema } from './schema';
export const items = withSchemaValidation( productsListSchema, ( state = {}, action ) => {
switch ( action.type ) {
case PRODUCTS_LIST_RECEIVE:
return action.productsList;
return { ...state.productsList, ...action.productsList };
}

return state;
Expand Down
24 changes: 11 additions & 13 deletions client/state/products-list/test/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,20 +24,18 @@ describe( 'reducer', () => {
} );

test( 'should store the product list received', () => {
const productsList = [
{
'business-bundle': {
available: true,
product_id: 1008,
product_name: 'WordPress.com Business',
product_slug: 'business-bundle',
is_domain_registration: false,
description: '',
cost: 300,
cost_display: '$300',
},
const productsList = {
'business-bundle': {
available: true,
product_id: 1008,
product_name: 'WordPress.com Business',
product_slug: 'business-bundle',
is_domain_registration: false,
description: '',
cost: 300,
cost_display: '$300',
},
];
};

const state = items(
{},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,8 @@ const useAddOnDisplayCost = ( productSlug: ProductsList.StoreProductSlug, quanti
const translate = useTranslate();
const prices = useAddOnPrices( productSlug, quantity );
const formattedCost = prices?.formattedMonthlyPrice || '';
const productsList = ProductsList.useProducts();
const productsList = ProductsList.useProducts( [ productSlug ] );
const product = productsList.data?.[ productSlug ];

if ( product?.term === 'month' ) {
/* Translators: %(formattedCost)s: monthly price formatted with currency */
return translate( '%(formattedCost)s/month, billed monthly', {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useMemo } from '@wordpress/element';
import * as ProductsList from '../../products-list';

const useAddOnPrices = ( productSlug: ProductsList.StoreProductSlug, quantity?: number ) => {
const productsList = ProductsList.useProducts();
const productsList = ProductsList.useProducts( [ productSlug ] );
const product = productsList.data?.[ productSlug ];

return useMemo( () => {
Expand Down
3 changes: 2 additions & 1 deletion packages/data-stores/src/add-ons/hooks/use-add-ons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,8 @@ const useAddOns = ( {
enableStorageAddOns,
}: Props = {} ): ( AddOnMeta | null )[] => {
const activeAddOns = useActiveAddOnsDefs( selectedSiteId );
const productsList = ProductsList.useProducts();
const productSlugs = activeAddOns.map( ( item ) => item.productSlug );
const productsList = ProductsList.useProducts( productSlugs );
const mediaStorage = Site.useSiteMediaStorage( { siteIdOrSlug: selectedSiteId } );
const siteFeatures = Site.useSiteFeatures( { siteIdOrSlug: selectedSiteId } );
const sitePurchases = Purchases.useSitePurchases( { siteId: selectedSiteId } );
Expand Down
11 changes: 9 additions & 2 deletions packages/data-stores/src/products-list/queries/use-products.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useQuery, type UseQueryResult } from '@tanstack/react-query';
import wpcomRequest from 'wpcom-proxy-request';
import * as ProductsList from '../../products-list';
import useQueryKeysFactory from './lib/use-query-keys-factory';
import type { StoreProductSlug, Product, RawAPIProductsList } from '../types';

Expand All @@ -12,15 +13,21 @@ type ProductsIndex = {
* - Only properties needed by the UI are included (which can gradually be expanded as needed)
* @returns Query result
*/
function useProducts(): UseQueryResult< ProductsIndex > {
function useProducts(
productSlugs?: ProductsList.StoreProductSlug[]
): UseQueryResult< ProductsIndex > {
const queryKeys = useQueryKeysFactory();
const product_slugs = productSlugs?.join( ',' ) ?? null;

return useQuery( {
queryKey: queryKeys.products(),
queryKey: [ ...queryKeys.products(), product_slugs ],
queryFn: async (): Promise< ProductsIndex > => {
const apiProducts: RawAPIProductsList = await wpcomRequest( {
path: `/products`,
apiVersion: '1.1',
...( product_slugs
? { query: new URLSearchParams( { product_slugs: product_slugs } ).toString() }
: {} ),
} );

return Object.fromEntries(
Expand Down

0 comments on commit b5eef14

Please sign in to comment.