Managing parameters (Shopify Hydrogen)

This is for:

Developer

When building your commerce interfaces via server-side rendering (SSR), you may want to manage URL parameters to synchronize them with the state of the interface. For example, when a user filters products on a search page, you might want to update the URL to reflect the applied filters.

The Headless React package provides a ParameterManager controller that lets you manage URL parameters in your commerce application.

Create a hook on the ParameterManager controller

Specify the ParameterManager controller in your commerce engine configuration.

// lib/commerce-engine-config.ts

import {
  defineParameterManager,
  // ...
} from '@coveo/headless-react/ssr-commerce';

export default {
  // ...
  controllers: {
    parameterManager: defineParameterManager(),
    // ...
  },
  // ...
};

Retrieve the ParameterManager controller hook after defining your engine.

// lib/commerce-engine.ts

import {defineCommerceEngine} from '@coveo/headless-react/ssr-commerce';
import engineConfig from './commerce-engine-config';

export const engineDefinition = defineCommerceEngine(engineConfig);

// ...

export const {
  useParameterManager,
  // ...
} = engineDefinition.controllers;

Create a component to manage URL parameters

Create a component that uses the ParameterManager controller hook to synchronize search parameters between the URL and Coveo’s engine state.

import {useParameterManager} from '../lib/coveo.engine';
import {buildParameterSerializer} from '@coveo/headless-react/ssr-commerce';
import {useSearchParams} from '@remix-run/react';
import {useEffect, useMemo, useRef} from 'react';

export default function ParameterManager({url}: {url: string | null}) {
  const {state, methods} = useParameterManager();

  const {serialize, deserialize} = buildParameterSerializer();

  const initialUrl = useMemo(() => new URL(url ?? ''), [url]);
  const previousUrl = useRef(initialUrl.href);
  const [searchParams] = useSearchParams();

  useEffect(() => { 1
    if (methods === undefined) { 2
      return;
    }

    const newCommerceParams = deserialize(searchParams);

    const newUrl = serialize(newCommerceParams, new URL(previousUrl.current));

    if (newUrl === previousUrl.current || newUrl === initialUrl.href) {
      return;
    }

    previousUrl.current = newUrl;
    methods.synchronize(newCommerceParams);
  }, [searchParams]);

  useEffect(() => { 3
    if (methods === undefined) {
      return;
    }

    const newUrl = serialize(state.parameters, new URL(previousUrl.current));

    if (previousUrl.current === newUrl || newUrl === initialUrl.href) {
      return;
    }

    previousUrl.current = newUrl;
    history.pushState(null, '', newUrl);
  }, [state.parameters]);

  return null;
}
1 When the URL search parameters change, this effect deserializes them and synchronizes them into the ParameterManager controller’s state.
2 Ensures that the effect only executes if the controller is {hydrated}, so that it plays well with the other effect. The same logic is added in the other effect.
3 When the ParameterManager controller’s state changes, this effect serializes it into the URL and pushes the new URL to the browser history.

Use your ParameterManager component

Integrate your ParameterManager component in your search pages to manage the URL parameters, as in the following example.

// app/routes/search.tsx

import {type LoaderFunctionArgs} from '@shopify/remix-oxygen';
import {useLoaderData, type MetaFunction} from '@remix-run/react';
import {
  fetchStaticState,
  searchEngineDefinition,
  type SearchStaticState,
} from '~/lib/coveo.engine';
import {
  ClientSideNavigatorContextProvider,
  ServerSideNavigatorContextProvider,
} from '~/lib/navigator.provider';
import {SearchProvider} from '~/components/Search/Context';
import {FullSearch} from '~/components/Search/FullSearch';
import ParameterManager from '~/components/ParameterManager';
import {buildParameterSerializer} from '@coveo/headless-react/ssr-commerce';
import {useEffect, useState} from 'react';

export const meta: MetaFunction = () => {
  return [{title: `Coveo | Search`}];
};

export type SearchLoader = typeof loader;

export async function loader({request, context}: LoaderFunctionArgs) {
  const url = new URL(request.url);

  const {deserialize} = buildParameterSerializer();
  const parameters = deserialize(url.searchParams); 1

  const q = url.searchParams.get('q') || '';

  searchEngineDefinition.setNavigatorContextProvider(
    () => new ServerSideNavigatorContextProvider(request),
  );

  const staticState = await fetchStaticState({
    context,
    k: 'searchEngineDefinition',
    parameters, 2
    url: `https://shop.barca.group`,
    request,
  });

  return {staticState, q, url};
}

export default function SearchPage() {
  const {staticState, q, url} = useLoaderData<typeof loader>();
  const [currentUrl, setCurrentUrl] = useState(url);

  useEffect(() => {
    setCurrentUrl(window.location.href);
  }, []);

  return (
    <SearchProvider
      navigatorContext={new ClientSideNavigatorContextProvider()}
      staticState={staticState as SearchStaticState}
    >
      <ParameterManager url={currentUrl} />3
       <!-- Rest of your components -->
    </SearchProvider>
  );
}
1 Deserialize the URL parameters using the deserialize function returned by the buildParameterSerializer function.
2 Set the deserialized parameters as the initial value when fetching the static state.
3 Use the ParameterManager component to manage the URL parameters in your search page.