Skip to content

Commit

Permalink
fix(core): dedupe requests in various pages (#1927)
Browse files Browse the repository at this point in the history
* fix(core): dedupe requests in product page

* fix(core): dedupe request in blog pages

* fix(core): add Variables type to webpages

* fix(core): add Variables type to getCart

* fix(core): dedupe requests in brand page

* fix(core): dedupe requests in category page

* fix(core): dedupe requests in search page

* chore: add changeset

* feat(core): add variables type to normal page

* fix: issue with missing currency code in quick search results

* fix: remove comment
  • Loading branch information
jorgemoya authored Jan 23, 2025
1 parent 62b891c commit 43351ab
Show file tree
Hide file tree
Showing 15 changed files with 244 additions and 199 deletions.
5 changes: 5 additions & 0 deletions .changeset/ninety-files-begin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@bigcommerce/catalyst-core": patch
---

Pass in currency code to quick search results.
5 changes: 5 additions & 0 deletions .changeset/warm-drinks-think.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@bigcommerce/catalyst-core": patch
---

Dedupe requests in various pages by properly caching/memoizing the function per page render.
58 changes: 32 additions & 26 deletions core/app/[locale]/(default)/(faceted)/brand/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,25 @@ import { fetchFacetedSearch } from '../../fetch-faceted-search';

import { getBrand as getBrandData } from './page-data';

interface Props {
params: Promise<{
slug: string;
locale: string;
}>;
searchParams: Promise<Record<string, string | string[] | undefined>>;
}
const cachedBrandDataVariables = cache((brandId: string) => {
return {
entityId: Number(brandId),
};
});

const cacheBrandFacetedSearch = cache((brandId: string) => {
return { brand: [brandId] };
});

function getBreadcrumbs(): Breadcrumb[] {
return [];
}

async function getBrand(props: Props) {
const { slug } = await props.params;
const brand = await getBrandData({ entityId: Number(slug) });

const variables = cachedBrandDataVariables(slug);
const brand = await getBrandData(variables);

if (brand == null) notFound();

Expand All @@ -46,15 +50,10 @@ async function getTitle(props: Props): Promise<string> {
return brand.name;
}

async function getTotalCount(props: Props): Promise<number> {
const search = await getRefinedSearch(props);

return search.products.collectionInfo?.totalItems ?? 0;
}

const createBrandSearchParamsCache = cache(async (props: Props) => {
const { slug } = await props.params;
const brandSearch = await fetchFacetedSearch({ brand: [slug] });
const brand = cacheBrandFacetedSearch(slug);
const brandSearch = await fetchFacetedSearch(brand);
const brandFacets = brandSearch.facets.items.filter(
(facet) => facet.__typename !== 'BrandSearchFilter',
);
Expand All @@ -80,7 +79,7 @@ const createBrandSearchParamsCache = cache(async (props: Props) => {
return createSearchParamsCache(filterParsers);
});

async function getRefinedSearch(props: Props) {
const getRefinedSearch = cache(async (props: Props) => {
const { slug } = await props.params;
const searchParams = await props.searchParams;
const searchParamsCache = await createBrandSearchParamsCache(props);
Expand All @@ -91,14 +90,21 @@ async function getRefinedSearch(props: Props) {
...parsedSearchParams,
brand: [slug],
});
});

async function getTotalCount(props: Props): Promise<number> {
const search = await getRefinedSearch(props);

return search.products.collectionInfo?.totalItems ?? 0;
}

async function getFilters(props: Props): Promise<Filter[]> {
const { slug } = await props.params;
const searchParams = await props.searchParams;
const searchParamsCache = await createBrandSearchParamsCache(props);
const parsedSearchParams = searchParamsCache?.parse(searchParams) ?? {};
const brandSearch = await fetchFacetedSearch({ brand: [slug] });
const brand = cacheBrandFacetedSearch(slug);
const brandSearch = await fetchFacetedSearch(brand);
const brandFacets = brandSearch.facets.items.filter(
(facet) => facet.__typename !== 'BrandSearchFilter',
);
Expand Down Expand Up @@ -149,15 +155,7 @@ async function getListProducts(props: Props): Promise<ListProduct[]> {
}

async function getPaginationInfo(props: Props): Promise<CursorPaginationInfo> {
const { slug } = await props.params;
const searchParams = await props.searchParams;
const searchParamsCache = await createBrandSearchParamsCache(props);
const parsedSearchParams = searchParamsCache?.parse(searchParams) ?? {};
const brandSearch = await fetchFacetedSearch({
...searchParams,
...parsedSearchParams,
brand: [slug],
});
const brandSearch = await getRefinedSearch(props);

return pageInfoTransformer(brandSearch.products.pageInfo);
}
Expand Down Expand Up @@ -210,6 +208,14 @@ async function getResetFiltersLabel(): Promise<string> {
return t('resetFilters');
}

interface Props {
params: Promise<{
slug: string;
locale: string;
}>;
searchParams: Promise<Record<string, string | string[] | undefined>>;
}

export async function generateMetadata(props: Props): Promise<Metadata> {
const brand = await getBrand(props);

Expand Down
130 changes: 64 additions & 66 deletions core/app/[locale]/(default)/(faceted)/category/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,9 @@ import { fetchFacetedSearch } from '../../fetch-faceted-search';
import { CategoryViewed } from './_components/category-viewed';
import { getCategoryPageData } from './page-data';

type SearchParams = Record<string, string | string[] | undefined>;

interface Props {
params: Promise<{
slug: string;
locale: string;
}>;
searchParams: Promise<SearchParams>;
}
const cacheCategoryFacetedSearch = cache((categoryId: string) => {
return { category: Number(categoryId) };
});

async function getCategory(props: Props) {
const { slug } = await props.params;
Expand All @@ -44,6 +38,49 @@ async function getCategory(props: Props) {
return category;
}

const createCategorySearchParamsCache = cache(async (props: Props) => {
const { slug } = await props.params;
const category = cacheCategoryFacetedSearch(slug);
const categorySearch = await fetchFacetedSearch(category);
const categoryFacets = categorySearch.facets.items.filter(
(facet) => facet.__typename !== 'CategorySearchFilter',
);
const transformedCategoryFacets = await facetsTransformer({
refinedFacets: categoryFacets,
allFacets: categoryFacets,
searchParams: {},
});
const categoryFilters = transformedCategoryFacets.filter((facet) => facet != null);
const filterParsers = getFilterParsers(categoryFilters);

// If there are no filters, return `null`, since calling `createSearchParamsCache` with an empty
// object will throw the following cryptic error:
//
// ```
// Error: [nuqs] Empty search params cache. Search params can't be accessed in Layouts.
// See https://err.47ng.com/NUQS-500
// ```
if (Object.keys(filterParsers).length === 0) {
return null;
}

return createSearchParamsCache(filterParsers);
});

const getRefinedSearch = cache(async (props: Props) => {
const { slug } = await props.params;
const categoryId = Number(slug);
const searchParams = await props.searchParams;
const searchParamsCache = await createCategorySearchParamsCache(props);
const parsedSearchParams = searchParamsCache?.parse(searchParams) ?? {};

return await fetchFacetedSearch({
...searchParams,
...parsedSearchParams,
category: categoryId,
});
});

async function getBreadcrumbs(props: Props): Promise<Breadcrumb[]> {
const category = await getCategory(props);

Expand Down Expand Up @@ -81,48 +118,11 @@ async function getTitle(props: Props): Promise<string | null> {
return category.name;
}

const createCategorySearchParamsCache = cache(async (props: Props) => {
const { slug } = await props.params;
const categorySearch = await fetchFacetedSearch({ category: Number(slug) });
const categoryFacets = categorySearch.facets.items.filter(
(facet) => facet.__typename !== 'CategorySearchFilter',
);
const transformedCategoryFacets = await facetsTransformer({
refinedFacets: categoryFacets,
allFacets: categoryFacets,
searchParams: {},
});
const categoryFilters = transformedCategoryFacets.filter((facet) => facet != null);
const filterParsers = getFilterParsers(categoryFilters);

// If there are no filters, return `null`, since calling `createSearchParamsCache` with an empty
// object will throw the following cryptic error:
//
// ```
// Error: [nuqs] Empty search params cache. Search params can't be accessed in Layouts.
// See https://err.47ng.com/NUQS-500
// ```
if (Object.keys(filterParsers).length === 0) {
return null;
}

return createSearchParamsCache(filterParsers);
});

async function getSearch(props: Props) {
const { slug } = await props.params;
const categoryId = Number(slug);
const searchParams = await props.searchParams;
const searchParamsCache = await createCategorySearchParamsCache(props);
const parsedSearchParams = searchParamsCache?.parse(searchParams) ?? {};
const search = await fetchFacetedSearch({
...searchParams,
...parsedSearchParams,
category: categoryId,
});
const getSearch = cache(async (props: Props) => {
const search = await getRefinedSearch(props);

return search;
}
});

async function getTotalCount(props: Props): Promise<number> {
const search = await getSearch(props);
Expand Down Expand Up @@ -154,16 +154,13 @@ async function getListProducts(props: Props): Promise<ListProduct[]> {

async function getFilters(props: Props): Promise<Filter[]> {
const { slug } = await props.params;
const categoryId = Number(slug);
const searchParams = await props.searchParams;
const searchParamsCache = await createCategorySearchParamsCache(props);
const parsedSearchParams = searchParamsCache?.parse(searchParams) ?? {};
const categorySearch = await fetchFacetedSearch({ category: categoryId });
const refinedSearch = await fetchFacetedSearch({
...searchParams,
...parsedSearchParams,
category: categoryId,
});
const category = cacheCategoryFacetedSearch(slug);
const categorySearch = await fetchFacetedSearch(category);
const refinedSearch = await getRefinedSearch(props);

const allFacets = categorySearch.facets.items.filter(
(facet) => facet.__typename !== 'CategorySearchFilter',
);
Expand Down Expand Up @@ -206,16 +203,7 @@ async function getSortOptions(): Promise<SortOption[]> {
}

async function getPaginationInfo(props: Props): Promise<CursorPaginationInfo> {
const { slug } = await props.params;
const categoryId = Number(slug);
const searchParams = await props.searchParams;
const searchParamsCache = await createCategorySearchParamsCache(props);
const parsedSearchParams = searchParamsCache?.parse(searchParams) ?? {};
const search = await fetchFacetedSearch({
...searchParams,
...parsedSearchParams,
category: categoryId,
});
const search = await getRefinedSearch(props);

return pageInfoTransformer(search.products.pageInfo);
}
Expand Down Expand Up @@ -262,6 +250,16 @@ async function getEmptyStateSubtitle(): Promise<string | null> {
return t('subtitle');
}

type SearchParams = Record<string, string | string[] | undefined>;

interface Props {
params: Promise<{
slug: string;
locale: string;
}>;
searchParams: Promise<SearchParams>;
}

export async function generateMetadata(props: Props): Promise<Metadata> {
const category = await getCategory(props);

Expand Down
33 changes: 19 additions & 14 deletions core/app/[locale]/(default)/(faceted)/search/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,6 @@ import { pricesTransformer } from '~/data-transformers/prices-transformer';

import { fetchFacetedSearch } from '../fetch-faceted-search';

interface Props {
searchParams: Promise<Record<string, string | string[] | undefined>>;
}

const createSearchSearchParamsCache = cache(async (props: Props) => {
const searchParams = await props.searchParams;
const search = await fetchFacetedSearch(searchParams);
Expand All @@ -45,21 +41,29 @@ const createSearchSearchParamsCache = cache(async (props: Props) => {
return createSearchParamsCache(filterParsers);
});

const getRefinedSearch = cache(async (props: Props) => {
const searchParams = await props.searchParams;
const searchParamsCache = await createSearchSearchParamsCache(props);
const parsedSearchParams = searchParamsCache?.parse(searchParams) ?? {};

return await fetchFacetedSearch({
...searchParams,
...parsedSearchParams,
});
});

async function getSearchTerm(props: Props): Promise<string> {
const searchParams = await props.searchParams;
const searchTerm = typeof searchParams.term === 'string' ? searchParams.term : '';

return searchTerm;
}

async function getSearch(props: Props) {
const searchParams = await props.searchParams;
const searchParamsCache = await createSearchSearchParamsCache(props);
const parsedSearchParams = searchParamsCache?.parse(searchParams) ?? {};
const search = await fetchFacetedSearch({ ...searchParams, ...parsedSearchParams });
const getSearch = cache(async (props: Props) => {
const search = await getRefinedSearch(props);

return search;
}
});

async function getProducts(props: Props) {
const searchTerm = await getSearchTerm(props);
Expand Down Expand Up @@ -89,10 +93,7 @@ async function getFilters(props: Props): Promise<Filter[]> {
let refinedSearch: Awaited<ReturnType<typeof fetchFacetedSearch>> | null = null;

if (searchTerm !== '') {
refinedSearch = await fetchFacetedSearch({
...searchParams,
...parsedSearchParams,
});
refinedSearch = await getRefinedSearch(props);
}

const categorySearch = await fetchFacetedSearch({});
Expand Down Expand Up @@ -237,6 +238,10 @@ async function getBreadcrumbs(): Promise<Breadcrumb[]> {
];
}

interface Props {
searchParams: Promise<Record<string, string | string[] | undefined>>;
}

export async function generateMetadata(): Promise<Metadata> {
const t = await getTranslations('Search');

Expand Down
8 changes: 5 additions & 3 deletions core/app/[locale]/(default)/blog/[blogId]/page-data.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { cache } from 'react';

import { client } from '~/client';
import { graphql } from '~/client/graphql';
import { graphql, VariablesOf } from '~/client/graphql';
import { revalidate } from '~/client/revalidate-target';

const BlogPageQuery = graphql(`
Expand Down Expand Up @@ -35,10 +35,12 @@ const BlogPageQuery = graphql(`
}
`);

export const getBlogPageData = cache(async ({ entityId }: { entityId: number }) => {
type Variables = VariablesOf<typeof BlogPageQuery>;

export const getBlogPageData = cache(async (variables: Variables) => {
const response = await client.fetch({
document: BlogPageQuery,
variables: { entityId },
variables,
fetchOptions: { next: { revalidate } },
});

Expand Down
Loading

0 comments on commit 43351ab

Please sign in to comment.