Category filter is not applied by InstantSearchNext when the url is opened via next/link
🐛 Current behavior
We are using InstantSearchNext to make SSR for Algolia search. The category is parsed from the url and passed to the uiState
export const InstantSearchProvider = ({ indexName, ...props }: Props) => {
return (
<InstantSearchNext
routing={{
router: {
cleanUrlOnDispose: false,
createURL({ qsModule, routeState, location }) {
// @ts-ignore
const queryParameters = routeState[indexName];
const queryString = qsModule.stringify(queryParameters, {
addQueryPrefix: true,
arrayFormat: 'brackets',
});
return `${location.origin}${location.pathname}${queryString}`;
},
parseURL({ location, qsModule }) {
const category = location.pathname.replace(/^\//, '').replace(/\/$/, '');
const params = qsModule.parse(location.search.slice(1));
const data = {
[indexName]: params,
};
if (!data[indexName].menu) {
// @ts-ignore
data[indexName].menu = {};
}
if (category.includes('/')) {
// @ts-ignore
data[indexName].menu['category.url'] = category;
} else {
// @ts-ignore
data[indexName].menu['category.parent.url'] = category;
}
return data;
},
},
stateMapping: {
stateToRoute(uiState) {
const indexUiState = uiState[indexName] || {};
if (indexUiState.configure?.['clickAnalytics']) {
// @ts-ignore
delete indexUiState.configure['clickAnalytics'];
}
if (indexUiState.menu?.['category.url']) {
delete indexUiState.menu['category.url'];
}
if (indexUiState.menu?.['category.parent.url']) {
delete indexUiState.menu['category.parent.url'];
}
return uiState;
},
routeToState(routeState: any) {
return routeState;
},
},
}}
indexName={indexName}
{...props}
/>
);
};
We have a component that has useMenu hook. It's visible when the page is opened via <a href="/categoryId">Category</a>
export const CategoryUrlFacet = ({ isSubCategory }: Props) => {
const attribute = isSubCategory ? 'category.url' : 'category.parent.url';
useMenu({ attribute: attribute, limit: 1 });
return null;
};
But when the page is opened via next/link <Link href="/categoryId">Category</Link> so that transition is seamless we got warning that menu widget is absent and the category parameter is ignored so we retrieve all the products.
The issue is reproduced when useDynamicWidgets is used together with useMenu is another component
export const FiltersColumn = ({
excludedAttributes = [],
isPlp = false,
isSubCategory = false,
className,
}: Props) => {
const preAppliedAttributes = isSubCategory ? ['category.url'] : ['category.parent.url'];
const trackFilterClick = useTrackClickFilter();
const { attributesToRender } = useDynamicWidgets({
facets: isPlp ? preAppliedAttributes : ['*'],
maxValuesPerFacet: 100,
});
const isCategoryPage = isPlp && !isSubCategory;
const filteredAttributes = attributesToRender.filter(
(attribute) => !excludedAttributes.includes(attribute),
);
return (
<div className={className}>
<div>Filters</div>
<div>
{isCategoryPage && <SubcategoryLinks />}
{filteredAttributes.map((attribute, index) => {
return (
<Filter
attribute={attribute}
key={attribute}
/>
);
})}
</div>
</div>
);
};
export const SubcategoryLinks = ({ onClick }: Props) => {
const { items } = useMenu({
attribute: 'category.nameUrl',
limit: 100,
transformItems,
});
return (
<ul>
{items.map((item, index) => (
<li key={item.label + index} >
<Link
href={`/${item.value}`}
>
<span>
{item.label}
</span>
<span> ({item.count})</span>
</Link>
</li>
))}
</ul>
);
};
🔍 Steps to reproduce
- Open the category page (page is showing the correct number of products)
- Open any product
- Go to the category page using next/link
- Result: Category filter wasn't applied. Search result consist of all the products (This issue is not consistent. It's reproduced time after time)
Live reproduction
https://codesandbox/no-sandbox
💭 Expected behavior
CSR page to return the same result as SSR. All filters that have been set up by routing prop of the InstantSearchNext component are applied.
Package version
algoliasearch 4.24.0, react-instantsearch 7.13.0, react-instantsearch-nextjs 0.3.10
Operating system
macOS 14.5
Browser
Chrome Version 128.0.6613.120
Code of Conduct
- [X] I agree to follow this project's Code of Conduct
@pkutsenko I have same issue, but for me it happens only if I have component with hook (eg. useRange, useRefinementList, useSortBy, ...) nested in more than 1 level in InstantSearch instance component (In my case Next.js with custom routing). I solved it by adding another one simple "virtual" component with hook directly into InstantSearch instance for each "hook" component.
<FiltersSectionWrapper>
<FiltersWrapper>
// components with useRefinementList defined by this component doesn't work with SSR
<Filters
showSearchBox
autoFocusSearchBox={autoFocusSearchBox}
searchBoxPlaceholder={t('Placeholder')}
items={[
{
type: 'range',
label: t('FilterOptions.Price'),
attribute: 'price'
},
{
type: 'toggle',
label: t('FilterOptions.Available'),
attribute: 'filterParameters.available'
},
{
type: 'toggle',
label: t('FilterOptions.FreeDelivery'),
attribute: 'filterParameters.freeDelivery'
}
]}
excludeAttributes={['categories.slug']}
/>
// SSR requires refinement used at this place, without this are refinements ignored on SSR
<VirtualRefinementList attribute='categories.slug' />
<VirtualRangeRefinement attribute='price' />
<VirtualToggleRefinement attribute='filterParameters.available' />
<VirtualToggleRefinement attribute='filterParameters.freeDelivery' />
</FiltersWrapper>
<SortingWrapper>
// component contains directly useSortBy, so this component works fine also with SSR
<Sorting items={sortingOptions} />
</SortingWrapper>
</FiltersSectionWrapper>
This is example content of VirtualRefinementList component
'use client'
import { useRefinementList, UseRefinementListProps } from 'react-instantsearch'
export default function VirtualRefinementList(props: UseRefinementListProps) {
useRefinementList(props)
return null
}
But I don't like this solution and I'm almost sure it si not correct usage. It works for now but I want to solve it in more elegant way.