@nosto/nosto-react 2.10.0 → 2.11.1
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 +460 -2
- package/dist/index.es.js +0 -1
- package/package.json +8 -7
- package/src/hooks/useDeepCompareEffect.ts +41 -0
- package/src/hooks/useLoadClientScript.ts +53 -0
- package/src/hooks/useNosto404.tsx +37 -1
- package/src/hooks/useNostoApi.ts +45 -0
- package/src/hooks/useNostoCategory.tsx +41 -0
- package/src/hooks/useNostoCheckout.tsx +40 -0
- package/src/hooks/useNostoContext.ts +39 -1
- package/src/hooks/useNostoHome.tsx +37 -0
- package/src/hooks/useNostoOrder.tsx +51 -0
- package/src/hooks/useNostoOther.tsx +58 -0
- package/src/hooks/useNostoProduct.tsx +52 -0
- package/src/hooks/useNostoSearch.tsx +49 -0
- package/src/hooks/useNostoSession.tsx +56 -0
- package/src/hooks/useRenderCampaigns.tsx +60 -1
package/dist/index.d.ts
CHANGED
|
@@ -437,7 +437,43 @@ export declare type ToCamelCase<T> = T extends (infer U)[] ? ToCamelCase<U>[] :
|
|
|
437
437
|
} : T;
|
|
438
438
|
|
|
439
439
|
/**
|
|
440
|
-
* You can personalise your
|
|
440
|
+
* You can personalise your 404 error pages by using the `useNosto404` hook.
|
|
441
|
+
*
|
|
442
|
+
* @example Basic 404 page usage
|
|
443
|
+
* ```tsx
|
|
444
|
+
* import { useNosto404 } from '@nosto/nosto-react'
|
|
445
|
+
*
|
|
446
|
+
* function NotFoundPage() {
|
|
447
|
+
* useNosto404({
|
|
448
|
+
* placements: ['notfound-nosto-1', 'notfound-popular-products']
|
|
449
|
+
* })
|
|
450
|
+
*
|
|
451
|
+
* return (
|
|
452
|
+
* <div>
|
|
453
|
+
* <h1>Page Not Found</h1>
|
|
454
|
+
* <p>Sorry, the page you're looking for doesn't exist.</p>
|
|
455
|
+
* <div id="notfound-popular-products" />
|
|
456
|
+
* <div id="notfound-nosto-1" />
|
|
457
|
+
* </div>
|
|
458
|
+
* )
|
|
459
|
+
* }
|
|
460
|
+
* ```
|
|
461
|
+
*
|
|
462
|
+
* @example 404 page with default placements
|
|
463
|
+
* ```tsx
|
|
464
|
+
* import { useNosto404 } from '@nosto/nosto-react'
|
|
465
|
+
*
|
|
466
|
+
* function Simple404Page() {
|
|
467
|
+
* useNosto404() // Uses all available placements
|
|
468
|
+
*
|
|
469
|
+
* return (
|
|
470
|
+
* <div>
|
|
471
|
+
* <h1>Oops! Page not found</h1>
|
|
472
|
+
* <p>Let us help you find what you're looking for:</p>
|
|
473
|
+
* </div>
|
|
474
|
+
* )
|
|
475
|
+
* }
|
|
476
|
+
* ```
|
|
441
477
|
*
|
|
442
478
|
* @group Hooks
|
|
443
479
|
*/
|
|
@@ -446,6 +482,47 @@ export declare function useNosto404(props?: Nosto404Props): void;
|
|
|
446
482
|
/**
|
|
447
483
|
* You can personalise your category and collection pages by using the useNostoCategory hook.
|
|
448
484
|
*
|
|
485
|
+
* @example Basic category page usage
|
|
486
|
+
* ```tsx
|
|
487
|
+
* import { useNostoCategory } from '@nosto/nosto-react'
|
|
488
|
+
*
|
|
489
|
+
* function CategoryPage({ categoryName }: { categoryName: string }) {
|
|
490
|
+
* useNostoCategory({
|
|
491
|
+
* category: categoryName,
|
|
492
|
+
* placements: ['categorypage-nosto-1', 'categorypage-nosto-2']
|
|
493
|
+
* })
|
|
494
|
+
*
|
|
495
|
+
* return (
|
|
496
|
+
* <div>
|
|
497
|
+
* <h1>Category: {categoryName}</h1>
|
|
498
|
+
* <div id="categorypage-nosto-1" />
|
|
499
|
+
* <div id="categorypage-nosto-2" />
|
|
500
|
+
* </div>
|
|
501
|
+
* )
|
|
502
|
+
* }
|
|
503
|
+
* ```
|
|
504
|
+
*
|
|
505
|
+
* @example Collection page with custom placements
|
|
506
|
+
* ```tsx
|
|
507
|
+
* import { useNostoCategory } from '@nosto/nosto-react'
|
|
508
|
+
*
|
|
509
|
+
* function CollectionPage({ collection }: { collection: { name: string, id: string } }) {
|
|
510
|
+
* useNostoCategory({
|
|
511
|
+
* category: `collection-${collection.id}`,
|
|
512
|
+
* placements: ['collection-banner', 'collection-recommendations']
|
|
513
|
+
* })
|
|
514
|
+
*
|
|
515
|
+
* return (
|
|
516
|
+
* <div>
|
|
517
|
+
* <h1>{collection.name} Collection</h1>
|
|
518
|
+
* <div id="collection-banner" />
|
|
519
|
+
* {/\* Product grid here *\/}
|
|
520
|
+
* <div id="collection-recommendations" />
|
|
521
|
+
* </div>
|
|
522
|
+
* )
|
|
523
|
+
* }
|
|
524
|
+
* ```
|
|
525
|
+
*
|
|
449
526
|
* @group Hooks
|
|
450
527
|
*/
|
|
451
528
|
export declare function useNostoCategory({ category, placements }: NostoCategoryProps): void;
|
|
@@ -453,6 +530,46 @@ export declare function useNostoCategory({ category, placements }: NostoCategory
|
|
|
453
530
|
/**
|
|
454
531
|
* You can personalise your cart and checkout pages by using the useNostoCheckout hook.
|
|
455
532
|
*
|
|
533
|
+
* @example Basic cart page usage
|
|
534
|
+
* ```tsx
|
|
535
|
+
* import { useNostoCheckout } from '@nosto/nosto-react'
|
|
536
|
+
*
|
|
537
|
+
* function CartPage() {
|
|
538
|
+
* useNostoCheckout({
|
|
539
|
+
* placements: ['cartpage-nosto-1', 'cartpage-cross-sell']
|
|
540
|
+
* })
|
|
541
|
+
*
|
|
542
|
+
* return (
|
|
543
|
+
* <div>
|
|
544
|
+
* <h1>Your Cart</h1>
|
|
545
|
+
* {/\* Cart items here *\/}
|
|
546
|
+
* <div id="cartpage-cross-sell" />
|
|
547
|
+
* <div id="cartpage-nosto-1" />
|
|
548
|
+
* </div>
|
|
549
|
+
* )
|
|
550
|
+
* }
|
|
551
|
+
* ```
|
|
552
|
+
*
|
|
553
|
+
* @example Checkout page with recommendations
|
|
554
|
+
* ```tsx
|
|
555
|
+
* import { useNostoCheckout } from '@nosto/nosto-react'
|
|
556
|
+
*
|
|
557
|
+
* function CheckoutPage() {
|
|
558
|
+
* useNostoCheckout({
|
|
559
|
+
* placements: ['checkout-upsell', 'checkout-last-chance']
|
|
560
|
+
* })
|
|
561
|
+
*
|
|
562
|
+
* return (
|
|
563
|
+
* <div>
|
|
564
|
+
* <h1>Checkout</h1>
|
|
565
|
+
* <div id="checkout-upsell" />
|
|
566
|
+
* {/\* Checkout form here *\/}
|
|
567
|
+
* <div id="checkout-last-chance" />
|
|
568
|
+
* </div>
|
|
569
|
+
* )
|
|
570
|
+
* }
|
|
571
|
+
* ```
|
|
572
|
+
*
|
|
456
573
|
* @group Hooks
|
|
457
574
|
*/
|
|
458
575
|
export declare function useNostoCheckout(props?: NostoCheckoutProps): void;
|
|
@@ -460,13 +577,88 @@ export declare function useNostoCheckout(props?: NostoCheckoutProps): void;
|
|
|
460
577
|
/**
|
|
461
578
|
* A hook that allows you to access the NostoContext and retrieve Nosto-related data from it in React components.
|
|
462
579
|
*
|
|
463
|
-
* @
|
|
580
|
+
* @example
|
|
581
|
+
* ```tsx
|
|
582
|
+
* import { useNostoContext } from '@nosto/nosto-react'
|
|
583
|
+
*
|
|
584
|
+
* function NostoStatus() {
|
|
585
|
+
* const { clientScriptLoaded, account, responseMode } = useNostoContext()
|
|
586
|
+
*
|
|
587
|
+
* return (
|
|
588
|
+
* <div>
|
|
589
|
+
* <p>Account: {account}</p>
|
|
590
|
+
* <p>Script loaded: {String(clientScriptLoaded)}</p>
|
|
591
|
+
* <p>Response mode: {responseMode}</p>
|
|
592
|
+
* </div>
|
|
593
|
+
* )
|
|
594
|
+
* }
|
|
595
|
+
* ```
|
|
596
|
+
*
|
|
597
|
+
* @example Using context for conditional rendering
|
|
598
|
+
* ```tsx
|
|
599
|
+
* import { useNostoContext } from '@nosto/nosto-react'
|
|
600
|
+
*
|
|
601
|
+
* function ConditionalRecommendations() {
|
|
602
|
+
* const { clientScriptLoaded, recommendationComponent } = useNostoContext()
|
|
603
|
+
*
|
|
604
|
+
* if (!clientScriptLoaded) {
|
|
605
|
+
* return <div>Loading recommendations...</div>
|
|
606
|
+
* }
|
|
607
|
+
*
|
|
608
|
+
* return (
|
|
609
|
+
* <div>
|
|
610
|
+
* {recommendationComponent && (
|
|
611
|
+
* <div id="nosto-recommendation-placeholder" />
|
|
612
|
+
* )}
|
|
613
|
+
* </div>
|
|
614
|
+
* )
|
|
615
|
+
* }
|
|
616
|
+
* ```
|
|
617
|
+
*
|
|
618
|
+
* @group Hooks
|
|
464
619
|
*/
|
|
465
620
|
export declare function useNostoContext(): NostoContextType;
|
|
466
621
|
|
|
467
622
|
/**
|
|
468
623
|
* You can personalise your home page by using the useNostoHome hook.
|
|
469
624
|
*
|
|
625
|
+
* @example Basic home page usage
|
|
626
|
+
* ```tsx
|
|
627
|
+
* import { useNostoHome } from '@nosto/nosto-react'
|
|
628
|
+
*
|
|
629
|
+
* function HomePage() {
|
|
630
|
+
* useNostoHome({
|
|
631
|
+
* placements: ['frontpage-nosto-1', 'frontpage-nosto-2', 'frontpage-hero']
|
|
632
|
+
* })
|
|
633
|
+
*
|
|
634
|
+
* return (
|
|
635
|
+
* <div>
|
|
636
|
+
* <div id="frontpage-hero" />
|
|
637
|
+
* <h1>Welcome to our store</h1>
|
|
638
|
+
* <div id="frontpage-nosto-1" />
|
|
639
|
+
* <div id="frontpage-nosto-2" />
|
|
640
|
+
* </div>
|
|
641
|
+
* )
|
|
642
|
+
* }
|
|
643
|
+
* ```
|
|
644
|
+
*
|
|
645
|
+
* @example Home page with default placements
|
|
646
|
+
* ```tsx
|
|
647
|
+
* import { useNostoHome } from '@nosto/nosto-react'
|
|
648
|
+
*
|
|
649
|
+
* function SimpleHomePage() {
|
|
650
|
+
* // Uses all available placements configured in Nosto admin
|
|
651
|
+
* useNostoHome()
|
|
652
|
+
*
|
|
653
|
+
* return (
|
|
654
|
+
* <div>
|
|
655
|
+
* <h1>Home Page</h1>
|
|
656
|
+
* {/\* Nosto will inject content into configured placements *\/}
|
|
657
|
+
* </div>
|
|
658
|
+
* )
|
|
659
|
+
* }
|
|
660
|
+
* ```
|
|
661
|
+
*
|
|
470
662
|
* @group Hooks
|
|
471
663
|
*/
|
|
472
664
|
export declare function useNostoHome(props?: NostoHomeProps): void;
|
|
@@ -474,6 +666,57 @@ export declare function useNostoHome(props?: NostoHomeProps): void;
|
|
|
474
666
|
/**
|
|
475
667
|
* You can personalise your order-confirmation/thank-you page by using the `useNostoOrder` hook.
|
|
476
668
|
*
|
|
669
|
+
* @example Basic order confirmation page usage
|
|
670
|
+
* ```tsx
|
|
671
|
+
* import { useNostoOrder } from '@nosto/nosto-react'
|
|
672
|
+
*
|
|
673
|
+
* function OrderConfirmationPage({ order }: { order: Order }) {
|
|
674
|
+
* useNostoOrder({
|
|
675
|
+
* order: order,
|
|
676
|
+
* placements: ['orderconfirm-nosto-1', 'orderconfirm-cross-sell']
|
|
677
|
+
* })
|
|
678
|
+
*
|
|
679
|
+
* return (
|
|
680
|
+
* <div>
|
|
681
|
+
* <h1>Order Confirmed!</h1>
|
|
682
|
+
* <p>Order #{order.order_number}</p>
|
|
683
|
+
* <div id="orderconfirm-cross-sell" />
|
|
684
|
+
* <div id="orderconfirm-nosto-1" />
|
|
685
|
+
* </div>
|
|
686
|
+
* )
|
|
687
|
+
* }
|
|
688
|
+
* ```
|
|
689
|
+
*
|
|
690
|
+
* @example Complete order with camelCase conversion
|
|
691
|
+
* ```tsx
|
|
692
|
+
* import { useNostoOrder } from '@nosto/nosto-react'
|
|
693
|
+
*
|
|
694
|
+
* function ThankYouPage({ orderData }: { orderData: ToCamelCase<Order> }) {
|
|
695
|
+
* useNostoOrder({
|
|
696
|
+
* order: {
|
|
697
|
+
* orderNumber: orderData.orderNumber,
|
|
698
|
+
* customerEmail: orderData.customerEmail,
|
|
699
|
+
* purchasedItems: orderData.purchasedItems.map(item => ({
|
|
700
|
+
* productId: item.productId,
|
|
701
|
+
* skuId: item.skuId,
|
|
702
|
+
* name: item.name,
|
|
703
|
+
* unitPrice: item.unitPrice,
|
|
704
|
+
* quantity: item.quantity
|
|
705
|
+
* }))
|
|
706
|
+
* },
|
|
707
|
+
* placements: ['thankyou-recommendations', 'thankyou-reviews']
|
|
708
|
+
* })
|
|
709
|
+
*
|
|
710
|
+
* return (
|
|
711
|
+
* <div>
|
|
712
|
+
* <h1>Thank you for your purchase!</h1>
|
|
713
|
+
* <div id="thankyou-recommendations" />
|
|
714
|
+
* <div id="thankyou-reviews" />
|
|
715
|
+
* </div>
|
|
716
|
+
* )
|
|
717
|
+
* }
|
|
718
|
+
* ```
|
|
719
|
+
*
|
|
477
720
|
* @group Hooks
|
|
478
721
|
*/
|
|
479
722
|
export declare function useNostoOrder({ order, placements }: NostoOrderProps): void;
|
|
@@ -481,6 +724,64 @@ export declare function useNostoOrder({ order, placements }: NostoOrderProps): v
|
|
|
481
724
|
/**
|
|
482
725
|
* You can personalise your miscellaneous pages by using the useNostoOther hook.
|
|
483
726
|
*
|
|
727
|
+
* @example Basic usage for contact page
|
|
728
|
+
* ```tsx
|
|
729
|
+
* import { useNostoOther } from '@nosto/nosto-react'
|
|
730
|
+
*
|
|
731
|
+
* function ContactPage() {
|
|
732
|
+
* useNostoOther({
|
|
733
|
+
* placements: ['contactpage-nosto-1', 'contactpage-popular-products']
|
|
734
|
+
* })
|
|
735
|
+
*
|
|
736
|
+
* return (
|
|
737
|
+
* <div>
|
|
738
|
+
* <h1>Contact Us</h1>
|
|
739
|
+
* <form>
|
|
740
|
+
* {/\* Contact form fields *\/}
|
|
741
|
+
* </form>
|
|
742
|
+
* <div id="contactpage-popular-products" />
|
|
743
|
+
* <div id="contactpage-nosto-1" />
|
|
744
|
+
* </div>
|
|
745
|
+
* )
|
|
746
|
+
* }
|
|
747
|
+
* ```
|
|
748
|
+
*
|
|
749
|
+
* @example About page with recommendations
|
|
750
|
+
* ```tsx
|
|
751
|
+
* import { useNostoOther } from '@nosto/nosto-react'
|
|
752
|
+
*
|
|
753
|
+
* function AboutPage() {
|
|
754
|
+
* useNostoOther({
|
|
755
|
+
* placements: ['aboutpage-featured', 'aboutpage-bestsellers']
|
|
756
|
+
* })
|
|
757
|
+
*
|
|
758
|
+
* return (
|
|
759
|
+
* <div>
|
|
760
|
+
* <h1>About Our Company</h1>
|
|
761
|
+
* <p>Learn more about our story...</p>
|
|
762
|
+
* <div id="aboutpage-featured" />
|
|
763
|
+
* <div id="aboutpage-bestsellers" />
|
|
764
|
+
* </div>
|
|
765
|
+
* )
|
|
766
|
+
* }
|
|
767
|
+
* ```
|
|
768
|
+
*
|
|
769
|
+
* @example Default placements for any other page
|
|
770
|
+
* ```tsx
|
|
771
|
+
* import { useNostoOther } from '@nosto/nosto-react'
|
|
772
|
+
*
|
|
773
|
+
* function GenericPage() {
|
|
774
|
+
* useNostoOther() // Uses all available placements
|
|
775
|
+
*
|
|
776
|
+
* return (
|
|
777
|
+
* <div>
|
|
778
|
+
* <h1>Generic Page</h1>
|
|
779
|
+
* <p>This could be any page type not covered by other hooks.</p>
|
|
780
|
+
* </div>
|
|
781
|
+
* )
|
|
782
|
+
* }
|
|
783
|
+
* ```
|
|
784
|
+
*
|
|
484
785
|
* @group Hooks
|
|
485
786
|
*/
|
|
486
787
|
export declare function useNostoOther(props?: NostoOtherProps): void;
|
|
@@ -488,6 +789,58 @@ export declare function useNostoOther(props?: NostoOtherProps): void;
|
|
|
488
789
|
/**
|
|
489
790
|
* You can personalise your product pages by using the useNostoProduct hook.
|
|
490
791
|
*
|
|
792
|
+
* @example Basic product page usage
|
|
793
|
+
* ```tsx
|
|
794
|
+
* import { useNostoProduct } from '@nosto/nosto-react'
|
|
795
|
+
*
|
|
796
|
+
* function ProductPage({ productId }: { productId: string }) {
|
|
797
|
+
* useNostoProduct({
|
|
798
|
+
* product: productId,
|
|
799
|
+
* placements: ['productpage-nosto-1', 'productpage-nosto-2']
|
|
800
|
+
* })
|
|
801
|
+
*
|
|
802
|
+
* return (
|
|
803
|
+
* <div>
|
|
804
|
+
* <h1>Product Details</h1>
|
|
805
|
+
* <div id="productpage-nosto-1" />
|
|
806
|
+
* <div id="productpage-nosto-2" />
|
|
807
|
+
* </div>
|
|
808
|
+
* )
|
|
809
|
+
* }
|
|
810
|
+
* ```
|
|
811
|
+
*
|
|
812
|
+
* @example Advanced product page with full tagging
|
|
813
|
+
* ```tsx
|
|
814
|
+
* import { useNostoProduct } from '@nosto/nosto-react'
|
|
815
|
+
*
|
|
816
|
+
* function AdvancedProductPage({ product }: { product: Product }) {
|
|
817
|
+
* useNostoProduct({
|
|
818
|
+
* product: product.id,
|
|
819
|
+
* reference: `product-${product.id}`,
|
|
820
|
+
* tagging: {
|
|
821
|
+
* product_id: product.id,
|
|
822
|
+
* name: product.name,
|
|
823
|
+
* url: product.url,
|
|
824
|
+
* price: product.price,
|
|
825
|
+
* list_price: product.listPrice,
|
|
826
|
+
* image_url: product.imageUrl,
|
|
827
|
+
* categories: product.categories,
|
|
828
|
+
* brand: product.brand,
|
|
829
|
+
* selected_sku_id: product.selectedVariant?.id
|
|
830
|
+
* },
|
|
831
|
+
* placements: ['productpage-cross-sell', 'productpage-upsell']
|
|
832
|
+
* })
|
|
833
|
+
*
|
|
834
|
+
* return (
|
|
835
|
+
* <div>
|
|
836
|
+
* <h1>{product.name}</h1>
|
|
837
|
+
* <div id="productpage-cross-sell" />
|
|
838
|
+
* <div id="productpage-upsell" />
|
|
839
|
+
* </div>
|
|
840
|
+
* )
|
|
841
|
+
* }
|
|
842
|
+
* ```
|
|
843
|
+
*
|
|
491
844
|
* @group Hooks
|
|
492
845
|
*/
|
|
493
846
|
export declare function useNostoProduct({ product, tagging, placements, reference }: NostoProductProps): void;
|
|
@@ -495,6 +848,55 @@ export declare function useNostoProduct({ product, tagging, placements, referenc
|
|
|
495
848
|
/**
|
|
496
849
|
* You can personalise your search pages by using the useNostoSearch hook.
|
|
497
850
|
*
|
|
851
|
+
* @example Basic search page usage
|
|
852
|
+
* ```tsx
|
|
853
|
+
* import { useNostoSearch } from '@nosto/nosto-react'
|
|
854
|
+
*
|
|
855
|
+
* function SearchPage({ searchQuery }: { searchQuery: string }) {
|
|
856
|
+
* useNostoSearch({
|
|
857
|
+
* query: searchQuery,
|
|
858
|
+
* placements: ['searchpage-nosto-1', 'searchpage-no-results']
|
|
859
|
+
* })
|
|
860
|
+
*
|
|
861
|
+
* return (
|
|
862
|
+
* <div>
|
|
863
|
+
* <h1>Search Results for "{searchQuery}"</h1>
|
|
864
|
+
* {/\* Search results here *\/}
|
|
865
|
+
* <div id="searchpage-nosto-1" />
|
|
866
|
+
* <div id="searchpage-no-results" />
|
|
867
|
+
* </div>
|
|
868
|
+
* )
|
|
869
|
+
* }
|
|
870
|
+
* ```
|
|
871
|
+
*
|
|
872
|
+
* @example Search with dynamic query updates
|
|
873
|
+
* ```tsx
|
|
874
|
+
* import { useNostoSearch } from '@nosto/nosto-react'
|
|
875
|
+
* import { useState } from 'react'
|
|
876
|
+
*
|
|
877
|
+
* function SearchPageWithFilters() {
|
|
878
|
+
* const [query, setQuery] = useState('')
|
|
879
|
+
*
|
|
880
|
+
* useNostoSearch({
|
|
881
|
+
* query: query,
|
|
882
|
+
* placements: ['search-recommendations', 'search-trending']
|
|
883
|
+
* })
|
|
884
|
+
*
|
|
885
|
+
* return (
|
|
886
|
+
* <div>
|
|
887
|
+
* <input
|
|
888
|
+
* type="text"
|
|
889
|
+
* value={query}
|
|
890
|
+
* onChange={(e) => setQuery(e.target.value)}
|
|
891
|
+
* placeholder="Search products..."
|
|
892
|
+
* />
|
|
893
|
+
* <div id="search-recommendations" />
|
|
894
|
+
* <div id="search-trending" />
|
|
895
|
+
* </div>
|
|
896
|
+
* )
|
|
897
|
+
* }
|
|
898
|
+
* ```
|
|
899
|
+
*
|
|
498
900
|
* @group Hooks
|
|
499
901
|
*/
|
|
500
902
|
export declare function useNostoSearch({ query, placements }: NostoSearchProps): void;
|
|
@@ -502,6 +904,62 @@ export declare function useNostoSearch({ query, placements }: NostoSearchProps):
|
|
|
502
904
|
/**
|
|
503
905
|
* 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.
|
|
504
906
|
*
|
|
907
|
+
* @example Basic session management
|
|
908
|
+
* ```tsx
|
|
909
|
+
* import { useNostoSession } from '@nosto/nosto-react'
|
|
910
|
+
*
|
|
911
|
+
* function App({ cart, user }: { cart?: Cart, user?: Customer }) {
|
|
912
|
+
* useNostoSession({
|
|
913
|
+
* cart: cart ? {
|
|
914
|
+
* items: cart.items.map(item => ({
|
|
915
|
+
* product_id: item.productId,
|
|
916
|
+
* sku_id: item.skuId,
|
|
917
|
+
* name: item.name,
|
|
918
|
+
* unit_price: item.price,
|
|
919
|
+
* quantity: item.quantity
|
|
920
|
+
* }))
|
|
921
|
+
* } : undefined,
|
|
922
|
+
* customer: user ? {
|
|
923
|
+
* customer_reference: user.id,
|
|
924
|
+
* email: user.email,
|
|
925
|
+
* first_name: user.firstName,
|
|
926
|
+
* last_name: user.lastName
|
|
927
|
+
* } : undefined
|
|
928
|
+
* })
|
|
929
|
+
*
|
|
930
|
+
* return <div>App content</div>
|
|
931
|
+
* }
|
|
932
|
+
* ```
|
|
933
|
+
*
|
|
934
|
+
* @example Route-based session updates
|
|
935
|
+
* ```tsx
|
|
936
|
+
* import { useNostoSession } from '@nosto/nosto-react'
|
|
937
|
+
* import { useEffect, useState } from 'react'
|
|
938
|
+
*
|
|
939
|
+
* function SessionProvider({ children }: { children: React.ReactNode }) {
|
|
940
|
+
* const [cart, setCart] = useState<Cart | null>(null)
|
|
941
|
+
* const [customer, setCustomer] = useState<Customer | null>(null)
|
|
942
|
+
*
|
|
943
|
+
* useEffect(() => {
|
|
944
|
+
* // Load cart and customer data
|
|
945
|
+
* loadCartData().then(setCart)
|
|
946
|
+
* loadCustomerData().then(setCustomer)
|
|
947
|
+
* }, [])
|
|
948
|
+
*
|
|
949
|
+
* useNostoSession({
|
|
950
|
+
* cart: cart ? {
|
|
951
|
+
* items: cart.items
|
|
952
|
+
* } : undefined,
|
|
953
|
+
* customer: customer ? {
|
|
954
|
+
* customerReference: customer.id,
|
|
955
|
+
* email: customer.email
|
|
956
|
+
* } : undefined
|
|
957
|
+
* })
|
|
958
|
+
*
|
|
959
|
+
* return <>{children}</>
|
|
960
|
+
* }
|
|
961
|
+
* ```
|
|
962
|
+
*
|
|
505
963
|
* @group Hooks
|
|
506
964
|
*/
|
|
507
965
|
export declare function useNostoSession({ cart, customer }?: NostoSessionProps): void;
|
package/dist/index.es.js
CHANGED
package/package.json
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nosto/nosto-react",
|
|
3
3
|
"description": "Component library to simply implementing Nosto on React.",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.11.1",
|
|
5
5
|
"author": "Mridang Agarwalla, Dominik Gilg",
|
|
6
6
|
"license": "ISC",
|
|
7
|
+
"type": "module",
|
|
7
8
|
"repository": {
|
|
8
9
|
"type": "git",
|
|
9
10
|
"url": "git+https://github.com/Nosto/nosto-react.git"
|
|
@@ -48,8 +49,8 @@
|
|
|
48
49
|
"@types/react": "^19.0.2",
|
|
49
50
|
"@types/react-dom": "^19.0.0",
|
|
50
51
|
"@types/user-event": "^4.1.1",
|
|
51
|
-
"@vitejs/plugin-react": "^5.
|
|
52
|
-
"@vitest/coverage-v8": "^
|
|
52
|
+
"@vitejs/plugin-react": "^5.1.1",
|
|
53
|
+
"@vitest/coverage-v8": "^4.0.10",
|
|
53
54
|
"eslint": "^9.13.0",
|
|
54
55
|
"eslint-plugin-barrel-files": "^3.0.1",
|
|
55
56
|
"eslint-plugin-promise": "^7.1.0",
|
|
@@ -66,18 +67,18 @@
|
|
|
66
67
|
"typedoc": "^0.28.9",
|
|
67
68
|
"typescript": "^5.9.2",
|
|
68
69
|
"typescript-eslint": "^8.39.0",
|
|
69
|
-
"vite": "^7.
|
|
70
|
+
"vite": "^7.2.2",
|
|
70
71
|
"vite-plugin-dts": "^4.2.2",
|
|
71
|
-
"vitest": "^
|
|
72
|
+
"vitest": "^4.0.10"
|
|
72
73
|
},
|
|
73
74
|
"main": "./dist/index.umd.js",
|
|
74
75
|
"module": "./dist/index.es.js",
|
|
75
76
|
"types": "./dist/index.d.ts",
|
|
76
77
|
"exports": {
|
|
77
78
|
".": {
|
|
79
|
+
"types": "./dist/index.d.ts",
|
|
78
80
|
"import": "./dist/index.es.js",
|
|
79
|
-
"require": "./dist/index.umd.js"
|
|
80
|
-
"types": "./dist/index.d.ts"
|
|
81
|
+
"require": "./dist/index.umd.js"
|
|
81
82
|
}
|
|
82
83
|
},
|
|
83
84
|
"bugs": {
|
|
@@ -1,6 +1,47 @@
|
|
|
1
1
|
import { useEffect, useRef, useMemo, type EffectCallback, type DependencyList } from "react"
|
|
2
2
|
import { deepCompare } from "../utils/compare"
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Similar to useEffect but performs deep comparison of dependencies instead of shallow comparison.
|
|
6
|
+
* Useful when dependencies are objects or arrays that may be recreated on each render.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```tsx
|
|
10
|
+
* import { useDeepCompareEffect } from '@nosto/nosto-react'
|
|
11
|
+
*
|
|
12
|
+
* function MyComponent({ user }: { user: { id: string, preferences: string[] } }) {
|
|
13
|
+
* useDeepCompareEffect(() => {
|
|
14
|
+
* console.log('User preferences changed:', user.preferences)
|
|
15
|
+
* // This will only run when user object actually changes,
|
|
16
|
+
* // not when it's recreated with same values
|
|
17
|
+
* }, [user])
|
|
18
|
+
*
|
|
19
|
+
* return <div>User: {user.id}</div>
|
|
20
|
+
* }
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* @example Comparing arrays and objects
|
|
24
|
+
* ```tsx
|
|
25
|
+
* import { useDeepCompareEffect } from '@nosto/nosto-react'
|
|
26
|
+
*
|
|
27
|
+
* function ProductList({ filters, sortOptions }: {
|
|
28
|
+
* filters: { category: string, price: { min: number, max: number } }
|
|
29
|
+
* sortOptions: string[]
|
|
30
|
+
* }) {
|
|
31
|
+
* useDeepCompareEffect(() => {
|
|
32
|
+
* // This effect will only run when filters or sortOptions actually change
|
|
33
|
+
* fetchProducts(filters, sortOptions)
|
|
34
|
+
* }, [filters, sortOptions])
|
|
35
|
+
*
|
|
36
|
+
* return <div>Product list</div>
|
|
37
|
+
* }
|
|
38
|
+
* ```
|
|
39
|
+
*
|
|
40
|
+
* @param callback The effect callback function
|
|
41
|
+
* @param dependencies Array of dependencies to deep compare
|
|
42
|
+
*
|
|
43
|
+
* @group Hooks
|
|
44
|
+
*/
|
|
4
45
|
export function useDeepCompareEffect(callback: EffectCallback, dependencies: DependencyList) {
|
|
5
46
|
return useEffect(callback, useDeepCompareMemoize(dependencies))
|
|
6
47
|
}
|
|
@@ -7,6 +7,59 @@ type NostoScriptProps = Pick<NostoProviderProps, "account" | "host" | "shopifyMa
|
|
|
7
7
|
|
|
8
8
|
const defaultAttributes = { "nosto-client-script": "" }
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Hook for loading the Nosto client script and managing its load state.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```tsx
|
|
15
|
+
* import { useLoadClientScript } from '@nosto/nosto-react'
|
|
16
|
+
*
|
|
17
|
+
* function MyNostoProvider() {
|
|
18
|
+
* const { clientScriptLoaded } = useLoadClientScript({
|
|
19
|
+
* account: 'shopify-123456',
|
|
20
|
+
* loadScript: true,
|
|
21
|
+
* shopifyMarkets: {
|
|
22
|
+
* marketId: 'US',
|
|
23
|
+
* language: 'en'
|
|
24
|
+
* }
|
|
25
|
+
* })
|
|
26
|
+
*
|
|
27
|
+
* return (
|
|
28
|
+
* <div>
|
|
29
|
+
* {clientScriptLoaded ? (
|
|
30
|
+
* <p>Nosto script loaded successfully</p>
|
|
31
|
+
* ) : (
|
|
32
|
+
* <p>Loading Nosto script...</p>
|
|
33
|
+
* )}
|
|
34
|
+
* </div>
|
|
35
|
+
* )
|
|
36
|
+
* }
|
|
37
|
+
* ```
|
|
38
|
+
*
|
|
39
|
+
* @example Custom script loader
|
|
40
|
+
* ```tsx
|
|
41
|
+
* import { useLoadClientScript } from '@nosto/nosto-react'
|
|
42
|
+
*
|
|
43
|
+
* function CustomNostoProvider() {
|
|
44
|
+
* const customScriptLoader = (url: string) => {
|
|
45
|
+
* const script = document.createElement('script')
|
|
46
|
+
* script.src = url
|
|
47
|
+
* script.async = true
|
|
48
|
+
* document.head.appendChild(script)
|
|
49
|
+
* return script
|
|
50
|
+
* }
|
|
51
|
+
*
|
|
52
|
+
* const { clientScriptLoaded } = useLoadClientScript({
|
|
53
|
+
* account: 'my-nosto-account',
|
|
54
|
+
* scriptLoader: customScriptLoader
|
|
55
|
+
* })
|
|
56
|
+
*
|
|
57
|
+
* return <div>Script loaded: {String(clientScriptLoaded)}</div>
|
|
58
|
+
* }
|
|
59
|
+
* ```
|
|
60
|
+
*
|
|
61
|
+
* @group Hooks
|
|
62
|
+
*/
|
|
10
63
|
export function useLoadClientScript(props: NostoScriptProps) {
|
|
11
64
|
const {
|
|
12
65
|
scriptLoader = scriptLoaderFn,
|
|
@@ -7,7 +7,43 @@ import { useRenderCampaigns } from "./useRenderCampaigns"
|
|
|
7
7
|
export type Nosto404Props = { placements?: string[] }
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
* You can personalise your
|
|
10
|
+
* You can personalise your 404 error pages by using the `useNosto404` hook.
|
|
11
|
+
*
|
|
12
|
+
* @example Basic 404 page usage
|
|
13
|
+
* ```tsx
|
|
14
|
+
* import { useNosto404 } from '@nosto/nosto-react'
|
|
15
|
+
*
|
|
16
|
+
* function NotFoundPage() {
|
|
17
|
+
* useNosto404({
|
|
18
|
+
* placements: ['notfound-nosto-1', 'notfound-popular-products']
|
|
19
|
+
* })
|
|
20
|
+
*
|
|
21
|
+
* return (
|
|
22
|
+
* <div>
|
|
23
|
+
* <h1>Page Not Found</h1>
|
|
24
|
+
* <p>Sorry, the page you're looking for doesn't exist.</p>
|
|
25
|
+
* <div id="notfound-popular-products" />
|
|
26
|
+
* <div id="notfound-nosto-1" />
|
|
27
|
+
* </div>
|
|
28
|
+
* )
|
|
29
|
+
* }
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* @example 404 page with default placements
|
|
33
|
+
* ```tsx
|
|
34
|
+
* import { useNosto404 } from '@nosto/nosto-react'
|
|
35
|
+
*
|
|
36
|
+
* function Simple404Page() {
|
|
37
|
+
* useNosto404() // Uses all available placements
|
|
38
|
+
*
|
|
39
|
+
* return (
|
|
40
|
+
* <div>
|
|
41
|
+
* <h1>Oops! Page not found</h1>
|
|
42
|
+
* <p>Let us help you find what you're looking for:</p>
|
|
43
|
+
* </div>
|
|
44
|
+
* )
|
|
45
|
+
* }
|
|
46
|
+
* ```
|
|
11
47
|
*
|
|
12
48
|
* @group Hooks
|
|
13
49
|
*/
|
package/src/hooks/useNostoApi.ts
CHANGED
|
@@ -4,6 +4,51 @@ import { useDeepCompareEffect } from "./useDeepCompareEffect"
|
|
|
4
4
|
import { nostojs } from "@nosto/nosto-js"
|
|
5
5
|
import { API } from "@nosto/nosto-js/client"
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Hook for executing code that requires access to the Nosto API.
|
|
9
|
+
* Waits for the client script to load before executing the callback.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```tsx
|
|
13
|
+
* import { useNostoApi } from '@nosto/nosto-react'
|
|
14
|
+
*
|
|
15
|
+
* function ProductRecommendations({ productId }: { productId: string }) {
|
|
16
|
+
* useNostoApi(async (api) => {
|
|
17
|
+
* const data = await api
|
|
18
|
+
* .defaultSession()
|
|
19
|
+
* .viewProduct(productId)
|
|
20
|
+
* .setPlacements(['productpage-nosto-1', 'productpage-nosto-2'])
|
|
21
|
+
* .load()
|
|
22
|
+
*
|
|
23
|
+
* console.log('Recommendations loaded:', data)
|
|
24
|
+
* }, [productId])
|
|
25
|
+
*
|
|
26
|
+
* return <div>Product recommendations will appear here</div>
|
|
27
|
+
* }
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* @example Using deep comparison for complex dependencies
|
|
31
|
+
* ```tsx
|
|
32
|
+
* import { useNostoApi } from '@nosto/nosto-react'
|
|
33
|
+
*
|
|
34
|
+
* function OrderThankYou({ order }: { order: Order }) {
|
|
35
|
+
* useNostoApi(async (api) => {
|
|
36
|
+
* await api
|
|
37
|
+
* .defaultSession()
|
|
38
|
+
* .addOrder(order)
|
|
39
|
+
* .load()
|
|
40
|
+
* }, [order], { deep: true })
|
|
41
|
+
*
|
|
42
|
+
* return <div>Thank you for your order!</div>
|
|
43
|
+
* }
|
|
44
|
+
* ```
|
|
45
|
+
*
|
|
46
|
+
* @param cb Callback function that receives the Nosto API instance
|
|
47
|
+
* @param deps Optional dependency list for useEffect
|
|
48
|
+
* @param flags Optional flags, including `deep` for deep comparison of dependencies
|
|
49
|
+
*
|
|
50
|
+
* @group Hooks
|
|
51
|
+
*/
|
|
7
52
|
export function useNostoApi(cb: (api: API) => void, deps?: DependencyList, flags?: { deep?: boolean }): void {
|
|
8
53
|
const { clientScriptLoaded } = useNostoContext()
|
|
9
54
|
const useEffectFn = flags?.deep ? useDeepCompareEffect : useEffect
|
|
@@ -12,6 +12,47 @@ export type NostoCategoryProps = {
|
|
|
12
12
|
/**
|
|
13
13
|
* You can personalise your category and collection pages by using the useNostoCategory hook.
|
|
14
14
|
*
|
|
15
|
+
* @example Basic category page usage
|
|
16
|
+
* ```tsx
|
|
17
|
+
* import { useNostoCategory } from '@nosto/nosto-react'
|
|
18
|
+
*
|
|
19
|
+
* function CategoryPage({ categoryName }: { categoryName: string }) {
|
|
20
|
+
* useNostoCategory({
|
|
21
|
+
* category: categoryName,
|
|
22
|
+
* placements: ['categorypage-nosto-1', 'categorypage-nosto-2']
|
|
23
|
+
* })
|
|
24
|
+
*
|
|
25
|
+
* return (
|
|
26
|
+
* <div>
|
|
27
|
+
* <h1>Category: {categoryName}</h1>
|
|
28
|
+
* <div id="categorypage-nosto-1" />
|
|
29
|
+
* <div id="categorypage-nosto-2" />
|
|
30
|
+
* </div>
|
|
31
|
+
* )
|
|
32
|
+
* }
|
|
33
|
+
* ```
|
|
34
|
+
*
|
|
35
|
+
* @example Collection page with custom placements
|
|
36
|
+
* ```tsx
|
|
37
|
+
* import { useNostoCategory } from '@nosto/nosto-react'
|
|
38
|
+
*
|
|
39
|
+
* function CollectionPage({ collection }: { collection: { name: string, id: string } }) {
|
|
40
|
+
* useNostoCategory({
|
|
41
|
+
* category: `collection-${collection.id}`,
|
|
42
|
+
* placements: ['collection-banner', 'collection-recommendations']
|
|
43
|
+
* })
|
|
44
|
+
*
|
|
45
|
+
* return (
|
|
46
|
+
* <div>
|
|
47
|
+
* <h1>{collection.name} Collection</h1>
|
|
48
|
+
* <div id="collection-banner" />
|
|
49
|
+
* {/\* Product grid here *\/}
|
|
50
|
+
* <div id="collection-recommendations" />
|
|
51
|
+
* </div>
|
|
52
|
+
* )
|
|
53
|
+
* }
|
|
54
|
+
* ```
|
|
55
|
+
*
|
|
15
56
|
* @group Hooks
|
|
16
57
|
*/
|
|
17
58
|
export function useNostoCategory({ category, placements }: NostoCategoryProps) {
|
|
@@ -9,6 +9,46 @@ export type NostoCheckoutProps = { placements?: string[] }
|
|
|
9
9
|
/**
|
|
10
10
|
* You can personalise your cart and checkout pages by using the useNostoCheckout hook.
|
|
11
11
|
*
|
|
12
|
+
* @example Basic cart page usage
|
|
13
|
+
* ```tsx
|
|
14
|
+
* import { useNostoCheckout } from '@nosto/nosto-react'
|
|
15
|
+
*
|
|
16
|
+
* function CartPage() {
|
|
17
|
+
* useNostoCheckout({
|
|
18
|
+
* placements: ['cartpage-nosto-1', 'cartpage-cross-sell']
|
|
19
|
+
* })
|
|
20
|
+
*
|
|
21
|
+
* return (
|
|
22
|
+
* <div>
|
|
23
|
+
* <h1>Your Cart</h1>
|
|
24
|
+
* {/\* Cart items here *\/}
|
|
25
|
+
* <div id="cartpage-cross-sell" />
|
|
26
|
+
* <div id="cartpage-nosto-1" />
|
|
27
|
+
* </div>
|
|
28
|
+
* )
|
|
29
|
+
* }
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* @example Checkout page with recommendations
|
|
33
|
+
* ```tsx
|
|
34
|
+
* import { useNostoCheckout } from '@nosto/nosto-react'
|
|
35
|
+
*
|
|
36
|
+
* function CheckoutPage() {
|
|
37
|
+
* useNostoCheckout({
|
|
38
|
+
* placements: ['checkout-upsell', 'checkout-last-chance']
|
|
39
|
+
* })
|
|
40
|
+
*
|
|
41
|
+
* return (
|
|
42
|
+
* <div>
|
|
43
|
+
* <h1>Checkout</h1>
|
|
44
|
+
* <div id="checkout-upsell" />
|
|
45
|
+
* {/\* Checkout form here *\/}
|
|
46
|
+
* <div id="checkout-last-chance" />
|
|
47
|
+
* </div>
|
|
48
|
+
* )
|
|
49
|
+
* }
|
|
50
|
+
* ```
|
|
51
|
+
*
|
|
12
52
|
* @group Hooks
|
|
13
53
|
*/
|
|
14
54
|
export function useNostoCheckout(props?: NostoCheckoutProps) {
|
|
@@ -4,7 +4,45 @@ import { NostoContext, NostoContextType } from "../context"
|
|
|
4
4
|
/**
|
|
5
5
|
* A hook that allows you to access the NostoContext and retrieve Nosto-related data from it in React components.
|
|
6
6
|
*
|
|
7
|
-
* @
|
|
7
|
+
* @example
|
|
8
|
+
* ```tsx
|
|
9
|
+
* import { useNostoContext } from '@nosto/nosto-react'
|
|
10
|
+
*
|
|
11
|
+
* function NostoStatus() {
|
|
12
|
+
* const { clientScriptLoaded, account, responseMode } = useNostoContext()
|
|
13
|
+
*
|
|
14
|
+
* return (
|
|
15
|
+
* <div>
|
|
16
|
+
* <p>Account: {account}</p>
|
|
17
|
+
* <p>Script loaded: {String(clientScriptLoaded)}</p>
|
|
18
|
+
* <p>Response mode: {responseMode}</p>
|
|
19
|
+
* </div>
|
|
20
|
+
* )
|
|
21
|
+
* }
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* @example Using context for conditional rendering
|
|
25
|
+
* ```tsx
|
|
26
|
+
* import { useNostoContext } from '@nosto/nosto-react'
|
|
27
|
+
*
|
|
28
|
+
* function ConditionalRecommendations() {
|
|
29
|
+
* const { clientScriptLoaded, recommendationComponent } = useNostoContext()
|
|
30
|
+
*
|
|
31
|
+
* if (!clientScriptLoaded) {
|
|
32
|
+
* return <div>Loading recommendations...</div>
|
|
33
|
+
* }
|
|
34
|
+
*
|
|
35
|
+
* return (
|
|
36
|
+
* <div>
|
|
37
|
+
* {recommendationComponent && (
|
|
38
|
+
* <div id="nosto-recommendation-placeholder" />
|
|
39
|
+
* )}
|
|
40
|
+
* </div>
|
|
41
|
+
* )
|
|
42
|
+
* }
|
|
43
|
+
* ```
|
|
44
|
+
*
|
|
45
|
+
* @group Hooks
|
|
8
46
|
*/
|
|
9
47
|
export function useNostoContext(): NostoContextType {
|
|
10
48
|
return useContext(NostoContext)
|
|
@@ -9,6 +9,43 @@ export type NostoHomeProps = { placements?: string[] }
|
|
|
9
9
|
/**
|
|
10
10
|
* You can personalise your home page by using the useNostoHome hook.
|
|
11
11
|
*
|
|
12
|
+
* @example Basic home page usage
|
|
13
|
+
* ```tsx
|
|
14
|
+
* import { useNostoHome } from '@nosto/nosto-react'
|
|
15
|
+
*
|
|
16
|
+
* function HomePage() {
|
|
17
|
+
* useNostoHome({
|
|
18
|
+
* placements: ['frontpage-nosto-1', 'frontpage-nosto-2', 'frontpage-hero']
|
|
19
|
+
* })
|
|
20
|
+
*
|
|
21
|
+
* return (
|
|
22
|
+
* <div>
|
|
23
|
+
* <div id="frontpage-hero" />
|
|
24
|
+
* <h1>Welcome to our store</h1>
|
|
25
|
+
* <div id="frontpage-nosto-1" />
|
|
26
|
+
* <div id="frontpage-nosto-2" />
|
|
27
|
+
* </div>
|
|
28
|
+
* )
|
|
29
|
+
* }
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* @example Home page with default placements
|
|
33
|
+
* ```tsx
|
|
34
|
+
* import { useNostoHome } from '@nosto/nosto-react'
|
|
35
|
+
*
|
|
36
|
+
* function SimpleHomePage() {
|
|
37
|
+
* // Uses all available placements configured in Nosto admin
|
|
38
|
+
* useNostoHome()
|
|
39
|
+
*
|
|
40
|
+
* return (
|
|
41
|
+
* <div>
|
|
42
|
+
* <h1>Home Page</h1>
|
|
43
|
+
* {/\* Nosto will inject content into configured placements *\/}
|
|
44
|
+
* </div>
|
|
45
|
+
* )
|
|
46
|
+
* }
|
|
47
|
+
* ```
|
|
48
|
+
*
|
|
12
49
|
* @group Hooks
|
|
13
50
|
*/
|
|
14
51
|
export function useNostoHome(props?: NostoHomeProps) {
|
|
@@ -15,6 +15,57 @@ export type NostoOrderProps = {
|
|
|
15
15
|
/**
|
|
16
16
|
* You can personalise your order-confirmation/thank-you page by using the `useNostoOrder` hook.
|
|
17
17
|
*
|
|
18
|
+
* @example Basic order confirmation page usage
|
|
19
|
+
* ```tsx
|
|
20
|
+
* import { useNostoOrder } from '@nosto/nosto-react'
|
|
21
|
+
*
|
|
22
|
+
* function OrderConfirmationPage({ order }: { order: Order }) {
|
|
23
|
+
* useNostoOrder({
|
|
24
|
+
* order: order,
|
|
25
|
+
* placements: ['orderconfirm-nosto-1', 'orderconfirm-cross-sell']
|
|
26
|
+
* })
|
|
27
|
+
*
|
|
28
|
+
* return (
|
|
29
|
+
* <div>
|
|
30
|
+
* <h1>Order Confirmed!</h1>
|
|
31
|
+
* <p>Order #{order.order_number}</p>
|
|
32
|
+
* <div id="orderconfirm-cross-sell" />
|
|
33
|
+
* <div id="orderconfirm-nosto-1" />
|
|
34
|
+
* </div>
|
|
35
|
+
* )
|
|
36
|
+
* }
|
|
37
|
+
* ```
|
|
38
|
+
*
|
|
39
|
+
* @example Complete order with camelCase conversion
|
|
40
|
+
* ```tsx
|
|
41
|
+
* import { useNostoOrder } from '@nosto/nosto-react'
|
|
42
|
+
*
|
|
43
|
+
* function ThankYouPage({ orderData }: { orderData: ToCamelCase<Order> }) {
|
|
44
|
+
* useNostoOrder({
|
|
45
|
+
* order: {
|
|
46
|
+
* orderNumber: orderData.orderNumber,
|
|
47
|
+
* customerEmail: orderData.customerEmail,
|
|
48
|
+
* purchasedItems: orderData.purchasedItems.map(item => ({
|
|
49
|
+
* productId: item.productId,
|
|
50
|
+
* skuId: item.skuId,
|
|
51
|
+
* name: item.name,
|
|
52
|
+
* unitPrice: item.unitPrice,
|
|
53
|
+
* quantity: item.quantity
|
|
54
|
+
* }))
|
|
55
|
+
* },
|
|
56
|
+
* placements: ['thankyou-recommendations', 'thankyou-reviews']
|
|
57
|
+
* })
|
|
58
|
+
*
|
|
59
|
+
* return (
|
|
60
|
+
* <div>
|
|
61
|
+
* <h1>Thank you for your purchase!</h1>
|
|
62
|
+
* <div id="thankyou-recommendations" />
|
|
63
|
+
* <div id="thankyou-reviews" />
|
|
64
|
+
* </div>
|
|
65
|
+
* )
|
|
66
|
+
* }
|
|
67
|
+
* ```
|
|
68
|
+
*
|
|
18
69
|
* @group Hooks
|
|
19
70
|
*/
|
|
20
71
|
export function useNostoOrder({ order, placements }: NostoOrderProps) {
|
|
@@ -9,6 +9,64 @@ export type NostoOtherProps = { placements?: string[] }
|
|
|
9
9
|
/**
|
|
10
10
|
* You can personalise your miscellaneous pages by using the useNostoOther hook.
|
|
11
11
|
*
|
|
12
|
+
* @example Basic usage for contact page
|
|
13
|
+
* ```tsx
|
|
14
|
+
* import { useNostoOther } from '@nosto/nosto-react'
|
|
15
|
+
*
|
|
16
|
+
* function ContactPage() {
|
|
17
|
+
* useNostoOther({
|
|
18
|
+
* placements: ['contactpage-nosto-1', 'contactpage-popular-products']
|
|
19
|
+
* })
|
|
20
|
+
*
|
|
21
|
+
* return (
|
|
22
|
+
* <div>
|
|
23
|
+
* <h1>Contact Us</h1>
|
|
24
|
+
* <form>
|
|
25
|
+
* {/\* Contact form fields *\/}
|
|
26
|
+
* </form>
|
|
27
|
+
* <div id="contactpage-popular-products" />
|
|
28
|
+
* <div id="contactpage-nosto-1" />
|
|
29
|
+
* </div>
|
|
30
|
+
* )
|
|
31
|
+
* }
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* @example About page with recommendations
|
|
35
|
+
* ```tsx
|
|
36
|
+
* import { useNostoOther } from '@nosto/nosto-react'
|
|
37
|
+
*
|
|
38
|
+
* function AboutPage() {
|
|
39
|
+
* useNostoOther({
|
|
40
|
+
* placements: ['aboutpage-featured', 'aboutpage-bestsellers']
|
|
41
|
+
* })
|
|
42
|
+
*
|
|
43
|
+
* return (
|
|
44
|
+
* <div>
|
|
45
|
+
* <h1>About Our Company</h1>
|
|
46
|
+
* <p>Learn more about our story...</p>
|
|
47
|
+
* <div id="aboutpage-featured" />
|
|
48
|
+
* <div id="aboutpage-bestsellers" />
|
|
49
|
+
* </div>
|
|
50
|
+
* )
|
|
51
|
+
* }
|
|
52
|
+
* ```
|
|
53
|
+
*
|
|
54
|
+
* @example Default placements for any other page
|
|
55
|
+
* ```tsx
|
|
56
|
+
* import { useNostoOther } from '@nosto/nosto-react'
|
|
57
|
+
*
|
|
58
|
+
* function GenericPage() {
|
|
59
|
+
* useNostoOther() // Uses all available placements
|
|
60
|
+
*
|
|
61
|
+
* return (
|
|
62
|
+
* <div>
|
|
63
|
+
* <h1>Generic Page</h1>
|
|
64
|
+
* <p>This could be any page type not covered by other hooks.</p>
|
|
65
|
+
* </div>
|
|
66
|
+
* )
|
|
67
|
+
* }
|
|
68
|
+
* ```
|
|
69
|
+
*
|
|
12
70
|
* @group Hooks
|
|
13
71
|
*/
|
|
14
72
|
export function useNostoOther(props?: NostoOtherProps) {
|
|
@@ -15,6 +15,58 @@ export type NostoProductProps = {
|
|
|
15
15
|
/**
|
|
16
16
|
* You can personalise your product pages by using the useNostoProduct hook.
|
|
17
17
|
*
|
|
18
|
+
* @example Basic product page usage
|
|
19
|
+
* ```tsx
|
|
20
|
+
* import { useNostoProduct } from '@nosto/nosto-react'
|
|
21
|
+
*
|
|
22
|
+
* function ProductPage({ productId }: { productId: string }) {
|
|
23
|
+
* useNostoProduct({
|
|
24
|
+
* product: productId,
|
|
25
|
+
* placements: ['productpage-nosto-1', 'productpage-nosto-2']
|
|
26
|
+
* })
|
|
27
|
+
*
|
|
28
|
+
* return (
|
|
29
|
+
* <div>
|
|
30
|
+
* <h1>Product Details</h1>
|
|
31
|
+
* <div id="productpage-nosto-1" />
|
|
32
|
+
* <div id="productpage-nosto-2" />
|
|
33
|
+
* </div>
|
|
34
|
+
* )
|
|
35
|
+
* }
|
|
36
|
+
* ```
|
|
37
|
+
*
|
|
38
|
+
* @example Advanced product page with full tagging
|
|
39
|
+
* ```tsx
|
|
40
|
+
* import { useNostoProduct } from '@nosto/nosto-react'
|
|
41
|
+
*
|
|
42
|
+
* function AdvancedProductPage({ product }: { product: Product }) {
|
|
43
|
+
* useNostoProduct({
|
|
44
|
+
* product: product.id,
|
|
45
|
+
* reference: `product-${product.id}`,
|
|
46
|
+
* tagging: {
|
|
47
|
+
* product_id: product.id,
|
|
48
|
+
* name: product.name,
|
|
49
|
+
* url: product.url,
|
|
50
|
+
* price: product.price,
|
|
51
|
+
* list_price: product.listPrice,
|
|
52
|
+
* image_url: product.imageUrl,
|
|
53
|
+
* categories: product.categories,
|
|
54
|
+
* brand: product.brand,
|
|
55
|
+
* selected_sku_id: product.selectedVariant?.id
|
|
56
|
+
* },
|
|
57
|
+
* placements: ['productpage-cross-sell', 'productpage-upsell']
|
|
58
|
+
* })
|
|
59
|
+
*
|
|
60
|
+
* return (
|
|
61
|
+
* <div>
|
|
62
|
+
* <h1>{product.name}</h1>
|
|
63
|
+
* <div id="productpage-cross-sell" />
|
|
64
|
+
* <div id="productpage-upsell" />
|
|
65
|
+
* </div>
|
|
66
|
+
* )
|
|
67
|
+
* }
|
|
68
|
+
* ```
|
|
69
|
+
*
|
|
18
70
|
* @group Hooks
|
|
19
71
|
*/
|
|
20
72
|
export function useNostoProduct({ product, tagging, placements, reference }: NostoProductProps) {
|
|
@@ -12,6 +12,55 @@ export type NostoSearchProps = {
|
|
|
12
12
|
/**
|
|
13
13
|
* You can personalise your search pages by using the useNostoSearch hook.
|
|
14
14
|
*
|
|
15
|
+
* @example Basic search page usage
|
|
16
|
+
* ```tsx
|
|
17
|
+
* import { useNostoSearch } from '@nosto/nosto-react'
|
|
18
|
+
*
|
|
19
|
+
* function SearchPage({ searchQuery }: { searchQuery: string }) {
|
|
20
|
+
* useNostoSearch({
|
|
21
|
+
* query: searchQuery,
|
|
22
|
+
* placements: ['searchpage-nosto-1', 'searchpage-no-results']
|
|
23
|
+
* })
|
|
24
|
+
*
|
|
25
|
+
* return (
|
|
26
|
+
* <div>
|
|
27
|
+
* <h1>Search Results for "{searchQuery}"</h1>
|
|
28
|
+
* {/\* Search results here *\/}
|
|
29
|
+
* <div id="searchpage-nosto-1" />
|
|
30
|
+
* <div id="searchpage-no-results" />
|
|
31
|
+
* </div>
|
|
32
|
+
* )
|
|
33
|
+
* }
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
* @example Search with dynamic query updates
|
|
37
|
+
* ```tsx
|
|
38
|
+
* import { useNostoSearch } from '@nosto/nosto-react'
|
|
39
|
+
* import { useState } from 'react'
|
|
40
|
+
*
|
|
41
|
+
* function SearchPageWithFilters() {
|
|
42
|
+
* const [query, setQuery] = useState('')
|
|
43
|
+
*
|
|
44
|
+
* useNostoSearch({
|
|
45
|
+
* query: query,
|
|
46
|
+
* placements: ['search-recommendations', 'search-trending']
|
|
47
|
+
* })
|
|
48
|
+
*
|
|
49
|
+
* return (
|
|
50
|
+
* <div>
|
|
51
|
+
* <input
|
|
52
|
+
* type="text"
|
|
53
|
+
* value={query}
|
|
54
|
+
* onChange={(e) => setQuery(e.target.value)}
|
|
55
|
+
* placeholder="Search products..."
|
|
56
|
+
* />
|
|
57
|
+
* <div id="search-recommendations" />
|
|
58
|
+
* <div id="search-trending" />
|
|
59
|
+
* </div>
|
|
60
|
+
* )
|
|
61
|
+
* }
|
|
62
|
+
* ```
|
|
63
|
+
*
|
|
15
64
|
* @group Hooks
|
|
16
65
|
*/
|
|
17
66
|
export function useNostoSearch({ query, placements }: NostoSearchProps) {
|
|
@@ -16,6 +16,62 @@ export type NostoSessionProps = {
|
|
|
16
16
|
/**
|
|
17
17
|
* 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.
|
|
18
18
|
*
|
|
19
|
+
* @example Basic session management
|
|
20
|
+
* ```tsx
|
|
21
|
+
* import { useNostoSession } from '@nosto/nosto-react'
|
|
22
|
+
*
|
|
23
|
+
* function App({ cart, user }: { cart?: Cart, user?: Customer }) {
|
|
24
|
+
* useNostoSession({
|
|
25
|
+
* cart: cart ? {
|
|
26
|
+
* items: cart.items.map(item => ({
|
|
27
|
+
* product_id: item.productId,
|
|
28
|
+
* sku_id: item.skuId,
|
|
29
|
+
* name: item.name,
|
|
30
|
+
* unit_price: item.price,
|
|
31
|
+
* quantity: item.quantity
|
|
32
|
+
* }))
|
|
33
|
+
* } : undefined,
|
|
34
|
+
* customer: user ? {
|
|
35
|
+
* customer_reference: user.id,
|
|
36
|
+
* email: user.email,
|
|
37
|
+
* first_name: user.firstName,
|
|
38
|
+
* last_name: user.lastName
|
|
39
|
+
* } : undefined
|
|
40
|
+
* })
|
|
41
|
+
*
|
|
42
|
+
* return <div>App content</div>
|
|
43
|
+
* }
|
|
44
|
+
* ```
|
|
45
|
+
*
|
|
46
|
+
* @example Route-based session updates
|
|
47
|
+
* ```tsx
|
|
48
|
+
* import { useNostoSession } from '@nosto/nosto-react'
|
|
49
|
+
* import { useEffect, useState } from 'react'
|
|
50
|
+
*
|
|
51
|
+
* function SessionProvider({ children }: { children: React.ReactNode }) {
|
|
52
|
+
* const [cart, setCart] = useState<Cart | null>(null)
|
|
53
|
+
* const [customer, setCustomer] = useState<Customer | null>(null)
|
|
54
|
+
*
|
|
55
|
+
* useEffect(() => {
|
|
56
|
+
* // Load cart and customer data
|
|
57
|
+
* loadCartData().then(setCart)
|
|
58
|
+
* loadCustomerData().then(setCustomer)
|
|
59
|
+
* }, [])
|
|
60
|
+
*
|
|
61
|
+
* useNostoSession({
|
|
62
|
+
* cart: cart ? {
|
|
63
|
+
* items: cart.items
|
|
64
|
+
* } : undefined,
|
|
65
|
+
* customer: customer ? {
|
|
66
|
+
* customerReference: customer.id,
|
|
67
|
+
* email: customer.email
|
|
68
|
+
* } : undefined
|
|
69
|
+
* })
|
|
70
|
+
*
|
|
71
|
+
* return <>{children}</>
|
|
72
|
+
* }
|
|
73
|
+
* ```
|
|
74
|
+
*
|
|
19
75
|
* @group Hooks
|
|
20
76
|
*/
|
|
21
77
|
export function useNostoSession({ cart, customer }: NostoSessionProps = {}) {
|
|
@@ -14,7 +14,6 @@ function RecommendationComponentWrapper(props: {
|
|
|
14
14
|
nostoRecommendation: Recommendation
|
|
15
15
|
}) {
|
|
16
16
|
return cloneElement(props.recommendationComponent, {
|
|
17
|
-
// eslint-disable-next-line react/prop-types
|
|
18
17
|
nostoRecommendation: props.nostoRecommendation
|
|
19
18
|
})
|
|
20
19
|
}
|
|
@@ -31,6 +30,66 @@ function injectCampaigns(data: CampaignData) {
|
|
|
31
30
|
injectPlacements(data.recommendations)
|
|
32
31
|
}
|
|
33
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Hook for rendering Nosto campaigns and recommendations. Handles both HTML and client-side rendering modes.
|
|
35
|
+
* This hook is typically used internally by other Nosto hooks but can be used directly for custom implementations.
|
|
36
|
+
*
|
|
37
|
+
* @example Basic usage with HTML mode
|
|
38
|
+
* ```tsx
|
|
39
|
+
* import { useRenderCampaigns } from '@nosto/nosto-react'
|
|
40
|
+
*
|
|
41
|
+
* function CustomRecommendations() {
|
|
42
|
+
* const { renderCampaigns } = useRenderCampaigns()
|
|
43
|
+
*
|
|
44
|
+
* useEffect(() => {
|
|
45
|
+
* // When using HTML response mode, campaigns are injected as HTML
|
|
46
|
+
* const campaignData = {
|
|
47
|
+
* campaigns: {
|
|
48
|
+
* content: {
|
|
49
|
+
* 'my-placement-id': '<div>Nosto content</div>'
|
|
50
|
+
* }
|
|
51
|
+
* },
|
|
52
|
+
* recommendations: {}
|
|
53
|
+
* }
|
|
54
|
+
* renderCampaigns(campaignData)
|
|
55
|
+
* }, [renderCampaigns])
|
|
56
|
+
*
|
|
57
|
+
* return <div id="my-placement-id" />
|
|
58
|
+
* }
|
|
59
|
+
* ```
|
|
60
|
+
*
|
|
61
|
+
* @example Client-side rendering with React components
|
|
62
|
+
* ```tsx
|
|
63
|
+
* import { useRenderCampaigns } from '@nosto/nosto-react'
|
|
64
|
+
*
|
|
65
|
+
* function ClientSideRecommendations() {
|
|
66
|
+
* const { renderCampaigns } = useRenderCampaigns()
|
|
67
|
+
*
|
|
68
|
+
* useEffect(() => {
|
|
69
|
+
* // When using client-side rendering, recommendations are rendered as React components
|
|
70
|
+
* const campaignData = {
|
|
71
|
+
* campaigns: {
|
|
72
|
+
* recommendations: {
|
|
73
|
+
* 'my-placement-id': {
|
|
74
|
+
* result_id: 'abc123',
|
|
75
|
+
* products: [
|
|
76
|
+
* { product_id: '1', name: 'Product 1', url: '/product/1' },
|
|
77
|
+
* { product_id: '2', name: 'Product 2', url: '/product/2' }
|
|
78
|
+
* ]
|
|
79
|
+
* }
|
|
80
|
+
* }
|
|
81
|
+
* },
|
|
82
|
+
* recommendations: {}
|
|
83
|
+
* }
|
|
84
|
+
* renderCampaigns(campaignData)
|
|
85
|
+
* }, [renderCampaigns])
|
|
86
|
+
*
|
|
87
|
+
* return <div id="my-placement-id" />
|
|
88
|
+
* }
|
|
89
|
+
* ```
|
|
90
|
+
*
|
|
91
|
+
* @group Hooks
|
|
92
|
+
*/
|
|
34
93
|
export function useRenderCampaigns() {
|
|
35
94
|
const { responseMode, recommendationComponent } = useNostoContext()
|
|
36
95
|
const placementRefs = useRef<Record<string, Root>>({})
|