diff --git a/src/components/Nosto404.tsx b/src/components/Nosto404.tsx index 1e1c692..2927dd9 100644 --- a/src/components/Nosto404.tsx +++ b/src/components/Nosto404.tsx @@ -1,5 +1,4 @@ -import { useNostoContext } from "./context" -import { useNostoApi } from "../utils/hooks" +import { useNostoContext, useNostoApi } from "../hooks" /** * You can personalise your cart and checkout pages by using the `Nosto404` component. diff --git a/src/components/NostoCategory.tsx b/src/components/NostoCategory.tsx index 5014d96..59a1951 100644 --- a/src/components/NostoCategory.tsx +++ b/src/components/NostoCategory.tsx @@ -1,5 +1,4 @@ -import { useNostoContext } from "./context" -import { useNostoApi } from "../utils/hooks" +import { useNostoContext, useNostoApi } from "../hooks" /** * You can personalise your category and collection pages by using the NostoCategory component. diff --git a/src/components/NostoCheckout.tsx b/src/components/NostoCheckout.tsx index aa641ea..82117f8 100644 --- a/src/components/NostoCheckout.tsx +++ b/src/components/NostoCheckout.tsx @@ -1,5 +1,4 @@ -import { useNostoContext } from "./context" -import { useNostoApi } from "../utils/hooks" +import { useNostoContext, useNostoApi } from "../hooks" /** * You can personalise your cart and checkout pages by using the NostoCheckout component. diff --git a/src/components/NostoHome.tsx b/src/components/NostoHome.tsx index 327b27c..0b76d65 100644 --- a/src/components/NostoHome.tsx +++ b/src/components/NostoHome.tsx @@ -1,5 +1,4 @@ -import { useNostoContext } from "./context" -import { useNostoApi } from "../utils/hooks" +import { useNostoContext, useNostoApi } from "../hooks" /** * The `NostoHome` component must be used to personalise the home page. The component does not require any props. diff --git a/src/components/NostoOrder.tsx b/src/components/NostoOrder.tsx index 3f58388..b4103f7 100644 --- a/src/components/NostoOrder.tsx +++ b/src/components/NostoOrder.tsx @@ -1,7 +1,6 @@ import { Order } from "../types" -import { useNostoContext } from "./context" +import { useNostoContext, useNostoApi } from "../hooks" import { snakeize } from "../utils/snakeize" -import { useNostoApi } from "../utils/hooks" /** * You can personalise your order-confirmation/thank-you page by using the `NostoOrder` component. diff --git a/src/components/NostoOther.tsx b/src/components/NostoOther.tsx index 21fb3fe..efd9c9f 100644 --- a/src/components/NostoOther.tsx +++ b/src/components/NostoOther.tsx @@ -1,5 +1,4 @@ -import { useNostoContext } from "./context" -import { useNostoApi } from "../utils/hooks" +import { useNostoContext, useNostoApi } from "../hooks" /** * You can personalise your miscellaneous pages by using the NostoOther component. diff --git a/src/components/NostoProduct.tsx b/src/components/NostoProduct.tsx index 910365d..b9f7575 100644 --- a/src/components/NostoProduct.tsx +++ b/src/components/NostoProduct.tsx @@ -1,5 +1,4 @@ -import { useNostoContext } from "./context" -import { useNostoApi } from "../utils/hooks" +import { useNostoContext, useNostoApi } from "../hooks" import { Product } from "../types" /** diff --git a/src/components/NostoProvider.tsx b/src/components/NostoProvider.tsx index 8e3d7aa..fbf1166 100644 --- a/src/components/NostoProvider.tsx +++ b/src/components/NostoProvider.tsx @@ -1,5 +1,5 @@ import React, { useEffect, isValidElement, useState, useRef } from "react" -import { NostoContext } from "./context" +import { NostoContext } from "../context" import { createRoot, Root } from "react-dom/client" import { NostoClient, Recommendation } from "../types" @@ -88,6 +88,7 @@ export default function NostoProvider(props: NostoProviderProps) { // custom hook for rendering campaigns (CSR/SSR): const [pageType, setPageType] = useState("") + function useRenderCampaigns(type: string = "") { const placementRefs = useRef>({}) useEffect(() => { @@ -130,6 +131,7 @@ export default function NostoProvider(props: NostoProviderProps) { } } } + return { renderCampaigns, pageTypeUpdated } } diff --git a/src/components/NostoSearch.tsx b/src/components/NostoSearch.tsx index c247253..2913ff6 100644 --- a/src/components/NostoSearch.tsx +++ b/src/components/NostoSearch.tsx @@ -1,5 +1,4 @@ -import { useNostoContext } from "./context" -import { useNostoApi } from "../utils/hooks" +import { useNostoContext, useNostoApi } from "../hooks" /** * You can personalise your search pages by using the NostoSearch component. diff --git a/src/components/NostoSession.tsx b/src/components/NostoSession.tsx index d9e9607..43f17d1 100644 --- a/src/components/NostoSession.tsx +++ b/src/components/NostoSession.tsx @@ -1,7 +1,6 @@ -import { useNostoContext } from "./context" +import { useNostoContext, useDeepCompareEffect } from "../hooks" import { Cart, Customer } from "../types" import { snakeize } from "../utils/snakeize" -import { useDeepCompareEffect } from "../utils/hooks" /** * Nosto React requires that you pass it the details of current cart contents and the details of the currently logged-in customer, if any, on every route change. diff --git a/src/components/index.ts b/src/components/index.ts index 7d6e43b..033bdaa 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -8,7 +8,4 @@ export { default as NostoOrder } from "./NostoOrder" export { default as NostoHome } from "./NostoHome" export { default as NostoPlacement } from "./NostoPlacement" export { default as NostoProvider } from "./NostoProvider" - -export { NostoContext, useNostoContext } from "./context" -export type { NostoContextType } from "./context" export { default as NostoSession } from "./NostoSession" diff --git a/src/components/context.ts b/src/context.ts similarity index 64% rename from src/components/context.ts rename to src/context.ts index 0da2737..526ec9e 100644 --- a/src/components/context.ts +++ b/src/context.ts @@ -1,5 +1,5 @@ -import { createContext, useContext } from "react" -import { NostoClient, Recommendation, RenderMode } from "../types" +import { createContext } from "react" +import { NostoClient, Recommendation, RenderMode } from "./types" type AnyFunction = (...args: unknown[]) => unknown @@ -39,17 +39,3 @@ export const NostoContext = createContext({ }, }) -/** - * A hook that allows you to access the NostoContext and retrieve Nosto-related data from it in React components. - * - * @group Essential Functions - */ -export function useNostoContext(): NostoContextType { - const context = useContext(NostoContext) - - if (!context) { - throw new Error("No nosto context found") - } - - return context -} diff --git a/src/hooks/index.ts b/src/hooks/index.ts new file mode 100644 index 0000000..a511d77 --- /dev/null +++ b/src/hooks/index.ts @@ -0,0 +1,3 @@ +export { useDeepCompareEffect } from "./useDeepCompareEffect" +export { useNostoApi } from "./useNostoApi" +export { useNostoContext } from "./useNostoContext" \ No newline at end of file diff --git a/src/hooks/useDeepCompareEffect.ts b/src/hooks/useDeepCompareEffect.ts new file mode 100644 index 0000000..a049481 --- /dev/null +++ b/src/hooks/useDeepCompareEffect.ts @@ -0,0 +1,21 @@ +import { useEffect, useRef, useMemo } from "react" +import { deepCompare } from "../utils/compare" + +export function useDeepCompareEffect( + callback: Parameters[0], + dependencies: Parameters[1] +): ReturnType { + return useEffect(callback, useDeepCompareMemoize(dependencies)) +} + +function useDeepCompareMemoize(value: T) { + const ref = useRef(value); + const signalRef = useRef(0) + + if (!deepCompare(value, ref.current)) { + ref.current = value + signalRef.current += 1 + } + + return useMemo(() => ref.current, [signalRef.current]) +} diff --git a/src/hooks/useNostoApi.ts b/src/hooks/useNostoApi.ts new file mode 100644 index 0000000..5d050b9 --- /dev/null +++ b/src/hooks/useNostoApi.ts @@ -0,0 +1,22 @@ +import { useEffect } from "react" +import { useNostoContext } from "./useNostoContext" +import { NostoClient } from "../types" +import { useDeepCompareEffect } from "./useDeepCompareEffect" + +export function useNostoApi( + cb: (api: NostoClient) => void, + deps?: React.DependencyList, + flags?: { deep?: boolean } +): void { + const { clientScriptLoaded, currentVariation, responseMode } = useNostoContext() + const useEffectFn = flags?.deep ? useDeepCompareEffect : useEffect + + useEffectFn(() => { + if (clientScriptLoaded) { + window.nostojs(api => { + api.defaultSession().setVariation(currentVariation!).setResponseMode(responseMode) + cb(api) + }) + } + }, [clientScriptLoaded, currentVariation, responseMode, ...(deps ?? [])]) +} diff --git a/src/hooks/useNostoContext.ts b/src/hooks/useNostoContext.ts new file mode 100644 index 0000000..3d3a5b7 --- /dev/null +++ b/src/hooks/useNostoContext.ts @@ -0,0 +1,18 @@ +import { useContext } from "react" +import { NostoContext, NostoContextType } from "../context" + +/** + * A hook that allows you to access the NostoContext and retrieve Nosto-related data from it in React components. + * + * @group Essential Functions + */ +export function useNostoContext(): NostoContextType { + const context = useContext(NostoContext) + + if (!context) { + throw new Error("No nosto context found") + } + + return context + } + \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index e80f5e4..b23d899 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,2 +1,4 @@ export type { Cart, Customer, Product, Order, Recommendation } from "./types" export * from "./components" +export { NostoContext, type NostoContextType } from "./context" +export { useNostoContext } from "./hooks/useNostoContext" \ No newline at end of file diff --git a/src/utils/hooks.ts b/src/utils/hooks.ts deleted file mode 100644 index b6dfdf4..0000000 --- a/src/utils/hooks.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { useEffect, useRef, useMemo } from "react" -import { useNostoContext } from "../components/context" -import { deepCompare } from "./compare" -import { NostoClient } from "../types" - -export function useDeepCompareEffect( - callback: Parameters[0], - dependencies: Parameters[1] -): ReturnType { - return useEffect(callback, useDeepCompareMemoize(dependencies)) -} - -function useDeepCompareMemoize(value: T) { - const ref = useRef(value) - const signalRef = useRef(0) - - if (!deepCompare(value, ref.current)) { - ref.current = value - signalRef.current += 1 - } - - return useMemo(() => ref.current, [signalRef.current]) -} - -export function useNostoApi( - cb: (api: NostoClient) => void, - deps?: React.DependencyList, - flags?: { deep?: boolean } -): void { - const { clientScriptLoaded, currentVariation, responseMode } = useNostoContext() - const useEffectFn = flags?.deep ? useDeepCompareEffect : useEffect - - useEffectFn(() => { - if (clientScriptLoaded) { - window.nostojs(api => { - api.defaultSession().setVariation(currentVariation!).setResponseMode(responseMode) - cb(api) - }) - } - }, [clientScriptLoaded, currentVariation, responseMode, ...(deps ?? [])]) -}