npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@crossingminds/beam-react

v0.11.9

Published

Utilities for adding Crossing Minds recommendations to React apps.

Downloads

9,864

Readme

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.

Type Documentation

For the most up-to-date information on exports and interfaces, please visit TS Docs.

Note: This documentation is currently in beta and works most of the time.

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

getItemRecommendations

Note: This function is currently in beta.

Retrieve item recommendations based on generic input. This functionality allows for testing various recommendation types without the need for code deployment.

/** Get item recommendations from a generic input.
 * @beta
 * @throws
 *
 * Supported recommendation types: item_to_items, profile_to_items, session_to_items, profile_to_items_w_ctx_items, session_to_items_w_ctx_items, precomputed_item_to_items, precomputed_profile_to_items, item_properties_to_items
 * Supported Generic Inputs: ItemToItems, UserToItems, SessionToItems, UserToItemsWithContext, SessionToItemsWithContext, PrecomputedItemToItems, PrecomputedUserToItems, ItemPropertiesToItems
 * Authenticates: https://docs.api.crossingminds.com/endpoints/account.html#login-as-service-account
 * Gets Item Recommendations from Generic Input: https://docs.api.crossingminds.com/endpoints/reco.html#get-items-recommendations-from-generic-input */
type GetItemRecommendationsParam<
  T extends keyof ItemRecommendationInputs | undefined
> = {
  databaseId: string
  password: string
  serviceLoginId: string
  clientOptions?:
    | Omit<RecommendationClientOptions, 'initialCredentials'>
    | undefined
} & GetGenericItemRecommendationsInput<T>

This function's input parameters depend on the generic T type parameter. T corresponds to recommendation types, and follows the options below:

  • item_to_items -> ItemToItems
  • profile_to_items -> UserToItems
  • session_to_items -> SessionToItems
  • profile_to_items_w_ctx_items -> UserToItemsWithContext
  • session_to_items_w_ctx_items -> SessionToItemsWithContext
  • precomputed_item_to_items -> PrecomputedItemToItems
  • precomputed_profile_to_items -> PrecomputedUserToItems
  • item_properties_to_items -> ItemPropertiesToItems

Example:

const recommendations = await getItemRecommendations<'ItemToItems' | 'PrecomputedItemToItems'>({
  databaseId: 'your-database-id',
  serviceLoginId: 'your-service-login-id',
  password: 'your-service-password',
  itemId: 'your-item-id'
})

Not specifying a recommendation type is the most flexible option and accepts all potential parameters. Parameters that are not applicable will be ignored silently.

getItemBasedRecommendations

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.
 * Authenticates: https://docs.api.crossingminds.com/endpoints/account.html#login-as-service-account
 * Gets related-item recommendations: https://docs.api.crossingminds.com/endpoints/reco.html#get-similar-items-recommendations */
type getRelatedItemRecommendationsParam = {
  databaseId: string
  password: string
  serviceLoginId: string
  scenario: string | typeof SCENARIO_OMITTED
  clientOptions?: OptimizedInputProperties['clientOptions'] | undefined
  itemId: GetRelatedItemRecommendationsInput['itemId']
  maxResults?: number | undefined
  cursor?: string | undefined
  filters?: GetRelatedItemRecommendationsInput['filters'] | undefined
  reranking?: GetRelatedItemRecommendationsInput['reranking'] | undefined
  skipDefaultScenario?: boolean | undefined
  userId?: string | undefined
  sessionId?: string | undefined
} & (
  | { userId?: string | undefined; sessionId: string }
  | { userId: string; sessionId?: string | 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.

/** 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
} & (
  | { userId?: string | undefined; sessionId: string }
  | { userId: string; 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
 * @throws */
type GetPersonalizedRecommendationsParam = {
  databaseId: string
  password: string
  serviceLoginId: string
  authLocalStorageKey?:
    | OptimizedInputProperties['authLocalStorageKey']
    | undefined
  contextItems?:
    | GetSessionWithContextItemBasedItemRecommendationsInput['contextItems']
    | GetUserWithContextItemBasedItemRecommendationsInput['contextItems']
    | undefined
  clientOptions?: OptimizedInputProperties['clientOptions'] | undefined
  cursor?: string | undefined
  excludeRatedItems?: boolean | undefined
  filters?: GetUserBasedItemRecommendationsInput['filters'] | undefined
  maxResults?: number | undefined
  reranking?: GetUserBasedItemRecommendationsInput['reranking'] | undefined
  sessionId?: string | undefined
  sessionScenario?: Scenario | undefined
  sessionWithContextScenario?: Scenario | undefined
  skipDefaultScenario?: boolean | undefined
  userId?: string | undefined
  userScenario?: Scenario | undefined
  userWithContextScenario?: Scenario | 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 = {
  databaseId: string
  password: string
  serviceLoginId: string
  clientOptions?: OptimizedInputProperties['clientOptions'] | undefined
  authLocalStorageKey?:
    | OptimizedInputProperties['authLocalStorageKey']
    | undefined
  /** @default 'browser' */
  runMode?: 'browser' | 'server' | 'always' | 'never' | undefined
} & PipelineOptions

type useRecordItemInteractionsOutput = {
  recordCustomItemInteraction: (input: {
    itemId: string
    interactionType: string
    properties?: SerializableObject | undefined
  }) => void
  recordAddItemToCartInteraction: (itemId: string) => void
  recordItemClickInteraction: (itemId: string) => void
  recordItemPageViewInteraction: (itemId: string) => void
  recordItemTransactionCompleteInteraction: (itemId: string) => 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
} & OptimizedInputProperties

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']
  /** Auth tokens are stored in local storage by default.
   * Providing your own localStorage key (property name) is recommended.
   * Only one set of auth tokens is stored per key. If you have multiple auth configurations, provide a different key for each.
   * If you don't want to use localStorage, set this to the `MEMORY_CACHE_ONLY` constant (exported by this package). */
  authLocalStorageKey?: AuthLocalStorageKey | undefined
  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.
    */
  }

}