0.10.0 • Published 5 days ago

@crossingminds/beam-react v0.10.0

Weekly downloads
-
License
-
Repository
-
Last release
5 days ago

Crossing Minds - Beam - React

Reusable React hooks and utilities for interacting with the Crossing Minds API. Beam is a customized recommendation platform, learn more here: https://www.crossingminds.com/beam

This library works with many flavors of React, including Remix-run, Shopify Hydrogen, and Next JS.

Prerequisites

Account details

You'll need the following Crossing Minds account details:

  • A service account serviceLoginId with a frontend role
  • A password/key for the service account
  • A databaseId in the same organization as your service account.

If you don't have a service account, you can add one in the Crossing Minds dashboard, or contact Crossing Minds directly.

individual or team accounts cannot be used with this library, only service accounts.

By default only accounts with a frontend role can be used with this library. This is because this library assumes lenient permissions where the password/key of frontend accounts is not secret, allowing for easier auth implementation.

If you need strict permissions you will have to handle logins and JWT tokens from your backend instead.

SessionId

sessionIds are required to use most parts of this library. As the name suggests, a sessionId is a unique id that is assigned to an end-user of your product. Every end-user must be assigned a unique sessionId. It must be a UUID, and it should remain consistent for that end-user across server and client/browser/app for as long as your product defines a session. You are responsible for generating and managing your app's session ids.

Note, userIds are different than sessionIds. userIds are only assigned to known end-users, and remain consistent for the lifetime of the end-user (permanent). sessionIds are assigned to all end-users including anonymous end-users.

Installation

Add this library to your react application using the package manager CLI of your choice (we like pnpm). Typescript types are included.

npm install @crossingminds/beam-react

Exports

getItemBasedRecommendations

Get recommendations for items based on an item id.

/** Get recommendations for items based on an item id.
 * Either a userId or a sessionId must be provided.
 * If a `userId` and a `sessionId` are provided, the `userId` will be used.
 * Gets item-based recommendations: https://docs.api.crossingminds.com/endpoints/reco.html#get-similar-items-recommendations */
type GetItemBasedRecommendationsParam = {
  databaseId: string;
  password: string;
  serviceLoginId: string;
  scenario: string | undefined;
  clientOptions?: OptimizedInputProperties["clientOptions"] | undefined;
  itemId: string;
  userId?: string | undefined;
  sessionId?: string | undefined;
  maxResults?: number | undefined;
  cursor?: string | undefined;
  filters?: GetRelatedItemRecommendationsInput["filters"] | undefined;
  reranking?: GetRelatedItemRecommendationsInput["reranking"] | undefined;
  skipDefaultScenario?: boolean | undefined;
};

getPrecomputedItemBasedRecommendations

Get precomputed recommendations for items based on an item id. This function is similar to getItemBasedRecommendations, but it uses precomputed recommendations instead of calculating them on the fly. This can be useful for performance or consistency.

/** Get precomputed recommendations for items based on an item id.
 * Either a userId or a sessionId must be provided.
 * If a `userId` and a `sessionId` are provided, the `userId` will be used.
 * Authenticates: https://docs.api.crossingminds.com/endpoints/account.html#login-as-service-account
 * Gets precomputed item-based recommendations: https://docs.api.crossingminds.com/endpoints/reco.html#get-precomputed-similar-items-recommendations */
type GetPrecomputedItemBasedRecommendationsParam = {
  databaseId: string;
  password: string;
  serviceLoginId: string;
  scenario: string;
  clientOptions?: OptimizedInputProperties["clientOptions"] | undefined;
  itemId: GetPrecomputedRelatedItemRecommendationsInput["itemId"];
  maxResults?: number | undefined;
  skipDefaultScenario?: boolean | undefined;
  userId?: string | undefined;
  sessionId?: string | undefined;
};

getPersonalizedRecommendations

Gets either sessionId or userId based recommendations.

/** Either a `userId` or a `sessionId` must be provided.
 * If both a `userId` and a `sessionId` are provided, user-based recommendations will be returned.
 * Authenticates: https://docs.api.crossingminds.com/endpoints/account.html#login-as-service-account
 * Gets session-based recommendations: https://docs.api.crossingminds.com/endpoints/reco.html#get-session-based-items-recommendations
 * Gets user-based recommendations: https://docs.api.crossingminds.com/endpoints/reco.html#get-profile-based-items-recommendations */
type GetPersonalizedRecommendationsParam = {
  sessionId?: string;
  userId?: string;
  databaseId: string;
  password: string;
  serviceLoginId: string;
  userScenario?: string | undefined;
  sessionScenario?: string | undefined;
  cursor?: string | undefined;
  excludeRatedItems?: boolean | undefined;
  filters?: GetUserBasedItemRecommendationsInput["filters"] | undefined;
  maxResults?: number | undefined;
  reranking?: GetUserBasedItemRecommendationsInput["reranking"] | undefined;
  skipDefaultScenario?: boolean | undefined;
  clientOptions?: OptimizedInputProperties["clientOptions"] | undefined;
};

useRecordItemInteractions

Hook that returns a set of functions used to record end-user interactions with items. Interactions are used to improve recommendations.

/** Either a `sessionId` or a `userId ` must be provided.
 * If both a `sessionId` and a `userId` are provided, the interaction will be recorded for the user AND the session will be "resolved".
 * Resolved sessions should not be used again to get recommendations or record interactions. */
type UseRecordItemInteractionsParam = {
  userId?: string;
  sessionId?: string;
  databaseId: string;
  password: string;
  serviceLoginId: string;
  clientOptions?: OptimizedInputProperties["clientOptions"] | undefined;
};

type useRecordItemInteractionsOutput = {
  recordAddItemToCartInteraction: (itemId: string) => Promise<void>;
  recordItemClickInteraction: (itemId: string) => Promise<void>;
  recordItemPageViewInteraction: (itemId: string) => Promise<void>;
  recordItemTransactionCompleteInteraction: (itemId: string) => Promise<void>;
  recordCustomItemInteraction: (input: {
    itemId: string;
    interactionType: string;
    properties?: Record<string, unknown> | undefined;
  }) => Promise<void>;
};

Hook that returns a set of functions used to record user interactions. Interactions are used to improve recommendations.

resolveSession

Associates a sessionId with a userId, "resolving" the session. This is critical for statistical analysis of logged-in users. If possible, this should only be called once per sessionId/userId pair.

type ResolveSessionIdParam = {
  sessionId: string;
  userId: string;
  serviceLoginId: LoginServiceInput["serviceLoginId"];
  password: LoginServiceInput["password"];
  databaseId: NonNullable<LoginServiceInput["databaseId"]>;
};

type OptimizedInputProperties

Properties included in input params of most functions. If these remain the same across multiple calls, some performance optimizations will be applied.

type OptimizedInputProperties = {
  serviceLoginId: LoginServiceInput["serviceLoginId"];
  password: LoginServiceInput["password"];
  databaseId: NonNullable<LoginServiceInput["databaseId"]>;
  sessionId?: NonNullable<LoginServiceInput["sessionId"]>;
  userId?: LoginServiceInput["userId"];
  clientOptions?:
    | Partial<Omit<RecommendationClientOptions, "initialCredentials">>
    | undefined;
};

Usage

Remix-run, Shopify Hydrogen with Oxygen (server-side)

With Remix and Hydrogen apps, we recommend using a loader function to fetch recommendations server-side. In this example we'll use Oxygen as our host, but you can use any host you'd like (example: import { json } from "@remix-run/node"). To add recommendations to a page, it only takes one function call, either getPersonalizedRecommendations for userId and sessionId based recommendations, or getItemBasedRecommendations for itemId based recommendations.

As mentioned in the SessionId section, you're responsible for managing your own SessionIds. These ids are extremely important for personalizing recommendations. The Hydrogen framework allows you to pass a session object into your loader function. Please see the Hydrogen docs for more information on how to use sessions.

If you'd like to get recommendations client-side in a Remix app, we recommend following the React Query example in the next section.

import {
  getPersonalizedRecommendations,
  type OptimizedInputProperties,
} from "@crossingminds/beam-react";
import { json, type LoaderArgs } from "@shopify/remix-oxygen";
import { useLoaderData } from "@remix-run/react";

/* Crossing Minds account details
This object is best stored and exported from common location and used throughout your app. 
You'll need it whenever you get new recommendations or record interactions.  */
const beamReactOptions = {
  databaseId: "UnIqUeIdOfMyDb",
  password: "really-more-of-an-api-key-than-a-password-1-!",
  serviceLoginId: "frontend-service-id-for-my-account",
} satisfies OptimizedInputProperties;

/* This is a typical loader function for Remix apps that will fetch the recommendations on the server
 https://shopify.dev/docs/custom-storefronts/hydrogen/data-fetching/fetch-data#load-data-and-query-the-storefront-api */
export async function loader({ params, context: { session } }: LoaderArgs) {
  const { itemIds } = await getPersonalizedRecommendations({
    ...beamReactOptions,
    sessionId: session.session.id,
  });

  /* We'll return our recommendations and our sessionId to the page component */
  return json({
    itemIds,
    sessionId,
  });
}

export default function Page() {
  /* Import the data from the server into your page component */
  const { itemIds, sessionId } = useLoaderData<typeof loader>();

  /* Use the itemIds to display your recommendations
  We're also passing in the sessionId so that we can record interactions with the items to make future recommendations better */
  return <BYOProductDisplay itemIds={itemIds} sessionId={sessionId} />;
}

React Query (client-side)

When fetching recommendations in a client (browser) environment, we recommend using React Query. Here's an example of getting item-based recommendations on a product page.

import React, { Fragment } from "react";
import { useQuery } from "@tanstack/react-query";
import { getItemBasedRecommendations } from "@crossingminds/beam-react";
import type { OptimizedInputProperties } from "@crossingminds/beam-react";

/* Crossing Minds account details */
const inputProperties = {
  databaseId: "UnIqUeIdOfMyDb",
  serviceLoginId: "frontend-service-id-for-my-account",
  password: "really-more-of-an-api-key-than-a-password-1-!",
} satisfies Partial<OptimizedInputProperties>;

interface ProductPageProps {
  itemId: string;
  sessionId: string;
}

/** Display item based recommendations */
export function ProductPage({ itemId, sessionId }: ProductPageProps) {
  const { data, isLoading, isError } = useQuery({
    queryKey: ["itemRecommendations", itemId, sessionId, inputProperties],
    retry: false, // getItemBasedRecommendations will retry internally
    refetchOnMount: true, // Fetch on every page view as the recommendations are dynamic
    staleTime: Infinity, // No need to refetch unless the page is reloaded
    refetchOnWindowFocus: false, // No need to refetch unless the page is reloaded
    cacheTime: 60 * 1000, // Recommendations will likely change as new interactions are recorded. This value will be based on the loading UX you're going for.

    async queryFn() {
      const { itemIds } = await getItemBasedRecommendations({
        ...inputProperties,
        itemId,
        sessionId,
      });

      return itemIds;
    },
  });

  return (
    /* This library does not include any UI. You'll be responsible for you own item display */
    <ProductDisplay itemIds={data} isLoading={isLoading} isError={isError} />
  );
}

Resolve a session

When both a userId and a sessionId are available, the session should be "resolved". This means that the session and user are linked. This is important for personalizing recommendations, as well as data analysis and reporting.

This can be done on the server or client. It's OK to continue using a sessionId after it has been resolved.

import { resolveSession } from "@crossingminds/beam-react";

...

if (sessionId && userId) {
  const isSessionResolved = await resolveSession({
    sessionId,
    userId,
    serviceLoginId,
    password,
    databaseId,
  });

  if (sessionIsResolved) {
    /*
    If possible, only call resolveSession once per sessionId.
    The function returns `true` when successful.
    You can then set a cookie, sessionStorage or localStorage value to avoid calling resolveSession again.
    */
  }

}
0.10.0

5 days ago

0.9.3

9 days ago

0.9.2

15 days ago

0.9.1

25 days ago

0.9.0

2 months ago

0.8.11

2 months ago

0.8.10

3 months ago

0.8.9

3 months ago

0.8.8

3 months ago

0.8.7

4 months ago

0.8.6

5 months ago

0.8.5

5 months ago

0.8.4

5 months ago

0.8.1

8 months ago

0.8.0

8 months ago

0.8.3

6 months ago

0.8.2

7 months ago

0.5.5

10 months ago

0.7.0

9 months ago

0.6.0

9 months ago

0.5.4

11 months ago

0.5.3

11 months ago

0.5.0

1 year ago

0.5.2

12 months ago

0.5.1

1 year ago

0.4.0

1 year ago

0.3.0

1 year ago

0.2.0

1 year ago

0.1.2

1 year ago

0.1.1

1 year ago

0.1.0

1 year ago