@nosto/nosto-react 1.0.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +86 -760
- package/dist/index.es.js +538 -569
- package/dist/index.umd.js +9 -9
- package/package.json +3 -1
- package/src/components/Nosto404.tsx +4 -9
- package/src/components/NostoCategory.tsx +4 -7
- package/src/components/NostoCheckout.tsx +4 -9
- package/src/components/NostoHome.tsx +4 -8
- package/src/components/NostoOrder.tsx +5 -7
- package/src/components/NostoOther.tsx +4 -9
- package/src/components/NostoProduct.tsx +4 -8
- package/src/components/NostoProvider.tsx +12 -68
- package/src/components/NostoSearch.tsx +4 -7
- package/src/components/NostoSession.tsx +1 -2
- package/src/components/helpers.ts +3 -0
- package/src/components/index.ts +0 -3
- package/src/context.ts +31 -0
- package/src/hooks/index.ts +4 -0
- package/src/hooks/useDeepCompareEffect.ts +21 -0
- package/src/hooks/useNostoApi.ts +22 -0
- package/src/hooks/useNostoContext.ts +18 -0
- package/src/hooks/useRenderCampaigns.tsx +60 -0
- package/src/index.ts +2 -0
- package/src/types.ts +73 -1
- package/src/components/context.ts +0 -55
- package/src/utils/hooks.ts +0 -41
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { useEffect, useRef, useMemo } from "react"
|
|
2
|
+
import { deepCompare } from "../utils/compare"
|
|
3
|
+
|
|
4
|
+
export function useDeepCompareEffect(
|
|
5
|
+
callback: Parameters<typeof useEffect>[0],
|
|
6
|
+
dependencies: Parameters<typeof useEffect>[1]
|
|
7
|
+
): ReturnType<typeof useEffect> {
|
|
8
|
+
return useEffect(callback, useDeepCompareMemoize(dependencies))
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function useDeepCompareMemoize<T>(value: T) {
|
|
12
|
+
const ref = useRef<T>(value);
|
|
13
|
+
const signalRef = useRef<number>(0)
|
|
14
|
+
|
|
15
|
+
if (!deepCompare(value, ref.current)) {
|
|
16
|
+
ref.current = value
|
|
17
|
+
signalRef.current += 1
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return useMemo(() => ref.current, [signalRef.current])
|
|
21
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { useEffect } from "react"
|
|
2
|
+
import { useNostoContext } from "./useNostoContext"
|
|
3
|
+
import { NostoClient } from "../types"
|
|
4
|
+
import { useDeepCompareEffect } from "./useDeepCompareEffect"
|
|
5
|
+
|
|
6
|
+
export function useNostoApi(
|
|
7
|
+
cb: (api: NostoClient) => void,
|
|
8
|
+
deps?: React.DependencyList,
|
|
9
|
+
flags?: { deep?: boolean }
|
|
10
|
+
): void {
|
|
11
|
+
const { clientScriptLoaded, currentVariation, responseMode } = useNostoContext()
|
|
12
|
+
const useEffectFn = flags?.deep ? useDeepCompareEffect : useEffect
|
|
13
|
+
|
|
14
|
+
useEffectFn(() => {
|
|
15
|
+
if (clientScriptLoaded) {
|
|
16
|
+
window.nostojs(api => {
|
|
17
|
+
api.defaultSession().setVariation(currentVariation!).setResponseMode(responseMode)
|
|
18
|
+
cb(api)
|
|
19
|
+
})
|
|
20
|
+
}
|
|
21
|
+
}, [clientScriptLoaded, currentVariation, responseMode, ...(deps ?? [])])
|
|
22
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { useContext } from "react"
|
|
2
|
+
import { NostoContext, NostoContextType } from "../context"
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A hook that allows you to access the NostoContext and retrieve Nosto-related data from it in React components.
|
|
6
|
+
*
|
|
7
|
+
* @group Essential Functions
|
|
8
|
+
*/
|
|
9
|
+
export function useNostoContext(): NostoContextType {
|
|
10
|
+
const context = useContext(NostoContext)
|
|
11
|
+
|
|
12
|
+
if (!context) {
|
|
13
|
+
throw new Error("No nosto context found")
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return context
|
|
17
|
+
}
|
|
18
|
+
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { useRef } from "react"
|
|
2
|
+
import { createRoot, Root } from "react-dom/client"
|
|
3
|
+
import { ActionResponse, Recommendation } from "../types"
|
|
4
|
+
import { useNostoContext } from "./useNostoContext"
|
|
5
|
+
import React from "react"
|
|
6
|
+
import { RecommendationComponent } from "../context"
|
|
7
|
+
|
|
8
|
+
// RecommendationComponent for client-side rendering:
|
|
9
|
+
function RecommendationComponentWrapper(props: {
|
|
10
|
+
recommendationComponent: RecommendationComponent,
|
|
11
|
+
nostoRecommendation: Recommendation }) {
|
|
12
|
+
|
|
13
|
+
return React.cloneElement(props.recommendationComponent, {
|
|
14
|
+
// eslint-disable-next-line react/prop-types
|
|
15
|
+
nostoRecommendation: props.nostoRecommendation,
|
|
16
|
+
})
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function injectCampaigns(data: ActionResponse) {
|
|
20
|
+
if (!window.nostojs) {
|
|
21
|
+
throw new Error("Nosto has not yet been initialized")
|
|
22
|
+
}
|
|
23
|
+
window.nostojs(api => {
|
|
24
|
+
api.placements.injectCampaigns(data.recommendations)
|
|
25
|
+
})
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function useRenderCampaigns() {
|
|
29
|
+
const { responseMode, recommendationComponent } = useNostoContext()
|
|
30
|
+
const placementRefs = useRef<Record<string, Root>>({})
|
|
31
|
+
|
|
32
|
+
if (responseMode == "HTML") {
|
|
33
|
+
return { renderCampaigns: injectCampaigns }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function renderCampaigns(data: ActionResponse) {
|
|
37
|
+
// render recommendation component into placements:
|
|
38
|
+
const recommendations = data.campaigns?.recommendations ?? {}
|
|
39
|
+
for (const key in recommendations) {
|
|
40
|
+
const recommendation = recommendations[key] as Recommendation
|
|
41
|
+
const placementSelector = "#" + key
|
|
42
|
+
const placementElement = document.querySelector(placementSelector)
|
|
43
|
+
|
|
44
|
+
if (placementElement) {
|
|
45
|
+
if (!placementRefs.current[key]) {
|
|
46
|
+
placementRefs.current[key] = createRoot(placementElement)
|
|
47
|
+
}
|
|
48
|
+
const root = placementRefs.current[key]!
|
|
49
|
+
root.render(
|
|
50
|
+
<RecommendationComponentWrapper
|
|
51
|
+
recommendationComponent={recommendationComponent!}
|
|
52
|
+
nostoRecommendation={recommendation}
|
|
53
|
+
></RecommendationComponentWrapper>
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return { renderCampaigns }
|
|
60
|
+
}
|
package/src/index.ts
CHANGED
package/src/types.ts
CHANGED
|
@@ -17,6 +17,7 @@ declare global {
|
|
|
17
17
|
export interface NostoClient {
|
|
18
18
|
setAutoLoad(autoload: boolean): void
|
|
19
19
|
defaultSession(): Session
|
|
20
|
+
listen(event: string, callback: (data: unknown) => void): void
|
|
20
21
|
placements: {
|
|
21
22
|
getPlacements(): string[]
|
|
22
23
|
injectCampaigns(recommendations: Record<string, unknown>): void
|
|
@@ -28,7 +29,7 @@ export interface NostoClient {
|
|
|
28
29
|
*/
|
|
29
30
|
export interface Recommendation {
|
|
30
31
|
result_id: string
|
|
31
|
-
products:
|
|
32
|
+
products: PushedProduct[]
|
|
32
33
|
result_type: string
|
|
33
34
|
title: string
|
|
34
35
|
div_id: string
|
|
@@ -36,6 +37,77 @@ export interface Recommendation {
|
|
|
36
37
|
params: unknown
|
|
37
38
|
}
|
|
38
39
|
|
|
40
|
+
export interface PushedProduct {
|
|
41
|
+
age_group?: string
|
|
42
|
+
alternate_image_urls: string[]
|
|
43
|
+
availability: string
|
|
44
|
+
brand?: string
|
|
45
|
+
category: string[]
|
|
46
|
+
category_id: string[]
|
|
47
|
+
condition?: string
|
|
48
|
+
custom_fields: { [index: string]: string }
|
|
49
|
+
date_published?: Date
|
|
50
|
+
description?: string
|
|
51
|
+
gender?: string
|
|
52
|
+
google_category?: string
|
|
53
|
+
gtin?: string
|
|
54
|
+
image_url?: string
|
|
55
|
+
inventory_level?: number
|
|
56
|
+
list_price?: number
|
|
57
|
+
name: string
|
|
58
|
+
parent_category_id: string[]
|
|
59
|
+
price: number
|
|
60
|
+
price_currency_code: string
|
|
61
|
+
product_id: string
|
|
62
|
+
rating_value?: number
|
|
63
|
+
review_count?: number
|
|
64
|
+
skus: PushedProductSKU[]
|
|
65
|
+
source_updated?: Date
|
|
66
|
+
supplier_cost?: number
|
|
67
|
+
tags1: string[]
|
|
68
|
+
tags2: string[]
|
|
69
|
+
tags3: string[]
|
|
70
|
+
thumb_url?: string
|
|
71
|
+
unit_pricing_base_measure?: number
|
|
72
|
+
unit_pricing_measure?: number
|
|
73
|
+
unit_pricing_unit?: string
|
|
74
|
+
update_received?: Date
|
|
75
|
+
url: string
|
|
76
|
+
variation_id?: string
|
|
77
|
+
variations: { [index: string]: PushedVariation }
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface PushedProductSKU extends NostoSku { }
|
|
81
|
+
|
|
82
|
+
export interface PushedVariation extends NostoVariant { }
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
export interface NostoSku extends Sku {
|
|
86
|
+
inventory_level?: number
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface NostoVariant {
|
|
90
|
+
availability: string
|
|
91
|
+
available: boolean
|
|
92
|
+
discounted: boolean
|
|
93
|
+
list_price?: number
|
|
94
|
+
price: number
|
|
95
|
+
price_currency_code: string
|
|
96
|
+
price_text?: string
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export interface Sku {
|
|
100
|
+
availability: string
|
|
101
|
+
custom_fields: { [index: string]: string }
|
|
102
|
+
gtin?: string
|
|
103
|
+
id: string
|
|
104
|
+
image_url?: string
|
|
105
|
+
list_price?: number
|
|
106
|
+
name: string
|
|
107
|
+
price: number
|
|
108
|
+
url?: string
|
|
109
|
+
}
|
|
110
|
+
|
|
39
111
|
// copied from client script d.ts export
|
|
40
112
|
declare const eventTypes: readonly ["vp", "lp", "dp", "rp", "bp", "vc", "or", "is", "cp", "ec", "es", "gc", "src", "cpr", "pl", "cc", "con"]
|
|
41
113
|
declare type EventType = typeof eventTypes[number]
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import { createContext, useContext } from "react"
|
|
2
|
-
import { NostoClient, Recommendation, RenderMode } from "../types"
|
|
3
|
-
|
|
4
|
-
type AnyFunction = (...args: unknown[]) => unknown
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* @group Types
|
|
8
|
-
*/
|
|
9
|
-
export interface NostoContextType {
|
|
10
|
-
account: string
|
|
11
|
-
clientScriptLoaded: boolean
|
|
12
|
-
currentVariation?: string
|
|
13
|
-
renderFunction?: AnyFunction
|
|
14
|
-
responseMode: RenderMode
|
|
15
|
-
recommendationComponent?: React.ReactElement<{
|
|
16
|
-
nostoRecommendation: Recommendation
|
|
17
|
-
}>
|
|
18
|
-
useRenderCampaigns(type: string): {
|
|
19
|
-
renderCampaigns(data: unknown, api: NostoClient): void
|
|
20
|
-
pageTypeUpdated: boolean
|
|
21
|
-
}
|
|
22
|
-
pageType: string
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* @group Essential Functions
|
|
27
|
-
*/
|
|
28
|
-
export const NostoContext = createContext<NostoContextType>({
|
|
29
|
-
account: "",
|
|
30
|
-
currentVariation: "",
|
|
31
|
-
pageType: "",
|
|
32
|
-
responseMode: "HTML",
|
|
33
|
-
clientScriptLoaded: false,
|
|
34
|
-
useRenderCampaigns: () => {
|
|
35
|
-
return {
|
|
36
|
-
renderCampaigns: () => {},
|
|
37
|
-
pageTypeUpdated: false,
|
|
38
|
-
}
|
|
39
|
-
},
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* A hook that allows you to access the NostoContext and retrieve Nosto-related data from it in React components.
|
|
44
|
-
*
|
|
45
|
-
* @group Essential Functions
|
|
46
|
-
*/
|
|
47
|
-
export function useNostoContext(): NostoContextType {
|
|
48
|
-
const context = useContext(NostoContext)
|
|
49
|
-
|
|
50
|
-
if (!context) {
|
|
51
|
-
throw new Error("No nosto context found")
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return context
|
|
55
|
-
}
|
package/src/utils/hooks.ts
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { useEffect, useRef, useMemo } from "react"
|
|
2
|
-
import { useNostoContext } from "../components/context"
|
|
3
|
-
import { deepCompare } from "./compare"
|
|
4
|
-
import { NostoClient } from "../types"
|
|
5
|
-
|
|
6
|
-
export function useDeepCompareEffect(
|
|
7
|
-
callback: Parameters<typeof useEffect>[0],
|
|
8
|
-
dependencies: Parameters<typeof useEffect>[1]
|
|
9
|
-
): ReturnType<typeof useEffect> {
|
|
10
|
-
return useEffect(callback, useDeepCompareMemoize(dependencies))
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function useDeepCompareMemoize<T>(value: T) {
|
|
14
|
-
const ref = useRef<T>(value)
|
|
15
|
-
const signalRef = useRef<number>(0)
|
|
16
|
-
|
|
17
|
-
if (!deepCompare(value, ref.current)) {
|
|
18
|
-
ref.current = value
|
|
19
|
-
signalRef.current += 1
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
return useMemo(() => ref.current, [signalRef.current])
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function useNostoApi(
|
|
26
|
-
cb: (api: NostoClient) => void,
|
|
27
|
-
deps?: React.DependencyList,
|
|
28
|
-
flags?: { deep?: boolean }
|
|
29
|
-
): void {
|
|
30
|
-
const { clientScriptLoaded, currentVariation, responseMode } = useNostoContext()
|
|
31
|
-
const useEffectFn = flags?.deep ? useDeepCompareEffect : useEffect
|
|
32
|
-
|
|
33
|
-
useEffectFn(() => {
|
|
34
|
-
if (clientScriptLoaded) {
|
|
35
|
-
window.nostojs(api => {
|
|
36
|
-
api.defaultSession().setVariation(currentVariation!).setResponseMode(responseMode)
|
|
37
|
-
cb(api)
|
|
38
|
-
})
|
|
39
|
-
}
|
|
40
|
-
}, [clientScriptLoaded, currentVariation, responseMode, ...(deps ?? [])])
|
|
41
|
-
}
|