This is the early access documentation preview for Custom Views. This documentation might not be in sync with our official documentation.

Extending the commercetools Frontend SDK

The following template file is provided to set up the SDK and integrations. These are the key aspects of the file:

packages/<project>/frontend/sdk/CommercetoolsSDK.tsTypeScript
import { SDK } from '@commercetools/frontend-sdk';
import {
ComposableCommerce,
ComposableCommerceEvents,
} from '@commercetools/frontend-composable-commerce';
// Add other integration's custom events to the SDK's generic type here,
// by extending ComposableCommerceEvents with their type using an intersection.
// For example, <ComposableCommerceEvents & OtherEvents>.
// You may also add your own custom events.
class CommercetoolsSDK extends SDK<ComposableCommerceEvents> {
composableCommerce!: ComposableCommerce;
// Add your other integrations here.
constructor() {
super();
this.composableCommerce = new ComposableCommerce(this);
// Initialize your other integrations here.
this.on('errorCaught', (event) => {
// Handle any integration errors caught by the SDK globally.
// For example, log error, fire notification, etc.
console.log('SDK error: ', event.data);
});
// Set up any other custom global event handlers here.
// Ensure types are created and added to the SDK generic type
// if specific to your project.
}
}
// Create a single instance of the SDK.
const sdk = new CommercetoolsSDK();
// Export only the instance to serve as a singleton throughout the project.
export { sdk };

CommercetoolsSDK class and ComposableCommerceEvents type

The CommercetoolsSDK class extends the SDK class from @commercetools/frontend-sdk, which provides the CommercetoolsSDK class with the methods and properties available in the SDK.

The ComposableCommerceEvents type is passed to the generic SDK argument. The ComposableCommerceEvents type comes from the @commercetools/frontend-composable-commerce integration package and specifies the types of any custom events of this particular package. In the SDK itself, this type is combined with the StandardEvents type with a type intersection. StandardEvents are standard events that an e-commerce integration is likely to implement (for example, cartFetched or productQueried).

If an event is not defined and passed to the generic SDK argument, triggering the event or adding a handler will cause a TypeScript compilation error.

Following is the pattern for extending event types and an implementation example.

Pattern to extend event typesTypeScript
export type Events = {
[key: string]: {
[key: string]: unknown;
};
};
Extend event typeTypeScript
export type CustomEventTriggers = {
someEventTriggered: { time: string };
someComponentClicked: { x: number; y: number };
};

CommercetoolsSDK.ts constructor

Two things happen in the CommercetoolsSDK.ts constructor:

  • Passing the SDK instance to the constructor in the form of this initializes the @commercetools/frontend-composable-commerce integration. This ensures that event triggers and handlers are set on the single instance of the @commercetools/frontend-sdk SDK and that actions that are called on the integration use the same SDK instance that was configured.

  • By default, a handler is set for the errorCaught event, which is triggered on the @commercetools/frontend-sdk SDK when an error occurs. You can deactivate the handler by commenting it out. However, it is useful for global error handling, such as logging the error or triggering a notification. You can also set other globally managed custom event handlers in the constructor.

    The SDK doesn't throw any errors. It returns { data: T, isError: false } on success or { isError: true, error: FetchError } on error.

Instance of the CommercetoolsSDK class

Instead of exporting the CommercetoolsSDK class itself, an instance of the class is created and exported. This way, when the SDK is imported anywhere in the project, the same instance is used. This ensures that once configured, the SDK is set up and integrations and event handlers are constructed and added only once.

If multiple instances of the CommercetoolsSDK class are created, the errorCaught handler will be added continuously and executed as many times as constructed. Furthermore, some integrations may add event handlers upon construction. Creating multiple instances of integrations will cause a similar bug.

Add integrations to the SDK

It is possible to extend the SDK with the integrations you develop. To do so, set up the integrations in the CommercetoolsSDK constructor, passing the SDK instance like the ComposableCommerce instance.

Additionally, any custom events must be added to the generic argument of SDK in the form of an intersection, as shown in the following example.

The backend actions must be added to your backend service to extend your extensions API.

Add custom events using intersectionTypeScript
class CommercetoolsSDK extends SDK<ComposableCommerceEvents & OtherIntegrationEvents> {
...
}

Create custom event triggers

commercetools Frontend SDK allows creating custom event handlers and triggering them within the project, such as when clicking on a particular component or holding the pointer over it. To add custom event triggers to the SDK event definition, create a file in the packages/<project>/frontend/sdk/ folder, as shown in the following example.

packages/<project>/frontend/sdk/MyCustomEvents.tsTypeScript
export type MyCustomEvents = {
marketingBannerClicked: { id: string };
marketingBannerHovered: { id: string; x: number; y: number };
};

Then, add the file you created to the CommercetoolsSDK class in the generic SDK argument. The file must be added in the form of an intersection, as shown in the following example.

packages/<project>/frontend/sdk/CommercetoolsSDK.tsTypeScript
import { SDK } from "@commercetools/frontend-sdk";
import {
ComposableCommerce,
ComposableCommerceEvents
} from "@commercetools/frontend-composable-commerce";
import { MyCustomEvents } from "./MyCustomEvents";
class CommercetoolsSDK extends SDK<ComposableCommerceEvents & MyCustomEvents> {
...
}
...

Once set, events can be triggered within your project, but you must clean up the event handlers set within components. Otherwise, handlers will stack on components remounting and may attempt to perform state updates on unmounted components, depending on the nature of the custom events.

Example of custom event trigger implementation

Following is an implementation example with the marketingBannerClicked event. In this example, the MarketingBanner component is given an onClick React event handler, which calls the sdk.trigger method.

An event constructed as the @commercetools/frontend-sdk exported class is passed to the sdk.trigger method. The event has the following properties, which match those previously defined in the MyCustomEvents.ts file:

  • eventName: 'marketingBannerClicked', which matches the key of the custom event.
  • data: { id: string }, which matches the value type of the custom event.

The named marketingBannerClickedHandler method is defined. In this method, an event parameter of type Event from the @commercetools/frontend-sdk library takes the following generic types:

  • EventName, matching the key/property name defined in the MyCustomEvents.ts file.
  • EventData, matching the key's value type.

This method is defined as a named function so that it is possible to remove the handler from the SDK.

The React useEffect component lifecycle hook is used to call sdk.on to set up the event handler on component mount and sdk.off to remove the handler on component unmount, passing the named handler on both occasions.

It is not required to set up event handlers within the same component, they can be set up anywhere in the project. It is often easier to set up the handlers within the CommercetoolsSDK constructor to avoid lifecycle bugs. The purpose of the following example is to describe event lifecycle management within components.

packages/<project>/frontend/components/marketingBanner/MarketingBanner.tsxTypeScript React
import Image from 'next/image';
import { useEffect } from 'react';
import { sdk } from '../../sdk';
import { Event } from '@commercetools/frontend-sdk';
interface Props {
marketingId: string;
imageSrc: string;
}
const MarketingBanner = ({ marketingId, imageSrc }: Props) => {
const marketingBannerClickedHandler = (
event: Event<
'marketingBannerClicked',
{
id: string;
}
>
) => {
// Perform custom event handling logic here.
console.log('Marketing banner clicked, id: ' + event.data.id);
};
const clickHandler = (id: string) => {
sdk.trigger(
new Event({
eventName: 'marketingBannerClicked',
data: {
id: id,
},
})
);
};
useEffect(() => {
sdk.on('marketingBannerClicked', marketingBannerClickedHandler);
return () => {
sdk.off('marketingBannerClicked', marketingBannerClickedHandler);
};
}, []);
return <Image src={imageSrc} onClick={() => clickHandler(marketingId)} />;
};
export default MarketingBanner;