Edit in GitHubLog an issue

Modify talon results

The talons target in Peregrine exposes talons as wrappable modules. This lets you define a wrapper module that intercepts a talon and let you modify the results.

Background#

In core Adobe Commerce and Magento Open Source development:

A plugin, or interceptor, is a class that modifies the behavior of public class functions by intercepting a function call and running code before, after, or around that function call. This allows you to substitute or extend the behavior of original, public methods for any class or interface.

See: Plugins (Interceptors)

PWA Studio's extensibility framework provides a similar feature that allows you to intercept a talon call and surround it with custom logic. This is useful if you want to add tracking logic or alter the incoming or outgoing values for a talon.

This tutorial teaches you how to create an extension that intercepts a talon and changes the results.

Tasks overview#

  1. Initialize the project
  2. Create and register an intercept file
  3. Define the intercept file
  4. Make a data fetch hook
  5. Create the talon wrapper module
  6. Update the package entry point
  7. Test on a local instance

Initialize the project#

To intercept and wrap a talon, you need a PWA Studio extension. Use npm init or yarn init to create a new JavaScript package project for this tutorial. Since, this is a standalone project, you do not need to create this inside a storefront project.

Edit the packages.json file so it looks like the following:

Copied to your clipboard
1{
2 "name": "my-extension",
3 "version": "0.0.1",
4 "description": "A PWA Studio extension",
5 "publishConfig": {
6 "access": "public"
7 },
8 "main": "index.js",
9 "license": "(OSL-3.0 OR AFL-3.0)",
10 "repository": "",
11 "dependencies": {},
12 "peerDependencies": {
13 "@magento/peregrine": "~7.0.0",
14 "@magento/pwa-buildpack": "~6.0.0",
15 "@magento/venia-ui": "~4.0.0",
16 "apollo-client": "2.6.4",
17 "graphql-tag": "~2.10.1",
18 "react": "~17.0.1",
19 "webpack": "~4.38.0"
20 }
21}

Create and register the intercept file#

You can create the intercept file anywhere in your project. For this tutorial, create this file under src/targets.

Copied to your clipboard
mkdir -p src/targets && touch src/targets/my-intercept.js

Set the value for pwa-studio.targets.intercept in your project's package.json file to tell the build process where to find the intercept file.

Copied to your clipboard
1 "react": "~17.0.1",
2 "webpack": "~4.38.0"
3 },
4+ "pwa-studio": {
5+ "targets": {
6+ "intercept": "src/targets/my-intercept"
7+ }
8+ }
9}

Define the intercept file#

The intercept file is where you tap into PWA Studio's extensibility framework and add your modifications.

In your intercept file, add the following content:

Copied to your clipboard
1module.exports = (targets) => {
2 // Wrap the useProductFullDetail talon with this extension
3 const peregrineTargets = targets.of("@magento/peregrine");
4 const talonsTarget = peregrineTargets.talons;
5
6 talonsTarget.tap((talonWrapperConfig) => {
7 talonWrapperConfig.ProductFullDetail.useProductFullDetail.wrapWith(
8 "my-extension"
9 );
10 });
11
12 // Set the buildpack features required by this extension
13 const builtins = targets.of("@magento/pwa-buildpack");
14 builtins.specialFeatures.tap((featuresByModule) => {
15 featuresByModule["@my-extension/my-product-page"] = {
16 // Wrapper modules must be ES Modules
17 esModules: true,
18 };
19 });
20};

When this file runs, it taps into the talonsTarget from the available targets in @magento/peregrine and wraps the useProductFullDetail() function call with your extension.

Since talon wrappers must be ES modules, this file also taps into the specialFeatures target from @magento/pwa-buildpack to set the esModules flag to true.

Make a data fetch hook#

Create a data fetch hook to query the backend for more data.

Copied to your clipboard
mkdir -p src/hooks && touch src/hooks/useProductCategoriesList.js

Inside the useProductCategoriesList.js file, add the following content:

Copied to your clipboard
1import { useMemo } from "react";
2import { useQuery } from "@apollo/react-hooks";
3import gql from "graphql-tag";
4
5const GET_PRODUCT_CATEGORIES = gql`
6 query getProductCategories($urlKey: String!) {
7 products(filter: { url_key: { eq: $urlKey } }) {
8 items {
9 categories {
10 name
11 url_path
12 }
13 }
14 }
15 }
16`;
17const useProductCategoriesList = (props) => {
18 const { urlKey } = props;
19
20 const { error, loading, data } = useQuery(GET_PRODUCT_CATEGORIES, {
21 fetchPolicy: "cache-and-network",
22 nextFetchPolicy: "cache-first",
23 variables: {
24 urlKey: urlKey,
25 },
26 });
27
28 const categories = useMemo(() => {
29 if (data && data.products.items[0]) {
30 return data.products.items[0].categories;
31 }
32 return null;
33 }, [data]);
34
35 return {
36 error,
37 isLoading: loading,
38 categories,
39 };
40};
41
42export default useProductCategoriesList;

This code defines a GraphQL query that fetches a list of category names and URL for a product at a specific URL key. It returns a hook that sends the query to the backend and returns data about the request, which includes the results.

Create the talon wrapper module#

A talon wrapper module wraps around an existing talon and injects code around the implementation logic the talon executes.

You can define this module anywhere in your project, but for this tutorial create it under src/targets.

Copied to your clipboard
touch src/targets/wrapper.js

Inside wrapper.js, add the following content:

Copied to your clipboard
1import useProductCategoriesList from "../hooks/useProductCategoriesList";
2
3const wrapUseProductFullDetails = (original) => {
4 return function useProductFullDetails(props, ...restArgs) {
5 console.log("Calling new useProductFullDetails() function!");
6
7 const { product } = props;
8
9 const categoriesListData = useProductCategoriesList({
10 urlKey: product.url_key,
11 });
12
13 const { productDetails, ...defaultReturnData } = original(
14 props,
15 ...restArgs
16 );
17
18 return {
19 ...defaultReturnData,
20 productDetails: {
21 ...productDetails,
22 categoriesList: categoriesListData,
23 },
24 };
25 };
26};
27
28export default wrapUseProductFullDetails;

This module exports a function that returns a new useProductFullDetails() function. Whenever a component calls the original useProductFullDetails() talon, it calls this new function instead.

The module imports and uses the useProductCategoriesList() hook defined in a previous step to get the categoriesListData, which contains information about the request. The code uses the props normally passed into the original function to get the value of the urlKey for the GraphQL request.

The extensibility framework provides the original talon function when it executes the wrapper module. The new useProductFullDetails() function calls this function and uses the props data result as a starting point for the final returned value.

The final return value is combination of the original props data and the categories list data returned by the hook.

Update the package entry point#

Update the package.json file and set the main entry point to the talon wrapper module.

Copied to your clipboard
1 "publishConfig": {
2 "access": "public"
3 },
4- "main": "index.js",
5+ "main": "src/targets/wrapper.js",
6 "license": "(OSL-3.0 OR AFL-3.0)",

This indicates which module to return when another project uses this package as a dependency. This is an important step because the intercept file in this project tells the extensibility framework to wrap the useProductFullDetails() talon with the module this package provides.

Test on a local instance#

Install this extension in a local storefront project to test its functionality.

Copied to your clipboard
yarn add --dev file:/path/to/your/extension/project

This will add a devDependencies entry to your storefront project's package.json that looks like the following:

Copied to your clipboard
1 "memory-fs": "~0.4.1",
2+ "my-extension": "file:/path/to/your/extension/project"
3 "prettier": "~1.16.4",

Now, when you navigate to a product page, the following message appears in the console:

Copied to your clipboard
"Calling new useProductFullDetails() function!".

Use additional console.log() calls to verify the application calls the data fetch hook.

To test the new props data the wrapped talon returns, you will need to create a copy of the ProductFullDetails component and alter it to log or render the new data.

  • Privacy
  • Terms of Use
  • Do not sell my personal information
  • AdChoices
Copyright © 2022 Adobe. All rights reserved.