Documentation Index
Fetch the complete documentation index at: https://docs.joinrefine.io/llms.txt
Use this file to discover all available pages before exploring further.
Overview
This guide covers patterns for integrating Refine into React applications, including custom hooks, context providers, and component patterns.Setup
Create SDK Instance
// lib/refine.ts
import { Refine } from '@refine-ai/sdk';
export const refine = new Refine({
apiKey: process.env.NEXT_PUBLIC_REFINE_API_KEY!,
organizationId: process.env.NEXT_PUBLIC_REFINE_ORG_ID!,
catalogId: process.env.NEXT_PUBLIC_REFINE_CATALOG_ID!
});
Context Provider
// contexts/RefineContext.tsx
import { createContext, useContext, ReactNode } from 'react';
import { Refine } from '@refine-ai/sdk';
import { refine } from '@/lib/refine';
const RefineContext = createContext<Refine | null>(null);
export function RefineProvider({ children }: { children: ReactNode }) {
return (
<RefineContext.Provider value={refine}>
{children}
</RefineContext.Provider>
);
}
export function useRefine(): Refine {
const context = useContext(RefineContext);
if (!context) {
throw new Error('useRefine must be used within RefineProvider');
}
return context;
}
Custom Hooks
useSearch
// hooks/useSearch.ts
import { useState, useCallback, useRef } from 'react';
import { refine } from '@/lib/refine';
import type { SearchResponse, Filter, SortBy } from '@refine-ai/sdk';
interface UseSearchOptions {
topK?: number;
visualWeight?: number;
filters?: Filter[];
sortBy?: SortBy;
}
interface UseSearchResult {
results: SearchResponse['results'];
totalResults: number;
isLoading: boolean;
error: Error | null;
search: (query: string) => Promise<void>;
trackClick: (productId: string, position: number) => void;
trackView: (productId: string, position: number) => void;
trackAddToCart: (productId: string) => void;
}
export function useSearch(options: UseSearchOptions = {}): UseSearchResult {
const [results, setResults] = useState<SearchResponse['results']>([]);
const [totalResults, setTotalResults] = useState(0);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<Error | null>(null);
const contextRef = useRef<any>(null);
const search = useCallback(async (query: string) => {
if (!query.trim()) {
setResults([]);
setTotalResults(0);
return;
}
setIsLoading(true);
setError(null);
try {
const response = await refine.search.text({
query,
topK: options.topK || 24,
visualWeight: options.visualWeight,
filters: options.filters,
sortBy: options.sortBy
});
setResults(response.results);
setTotalResults(response.totalResults);
// Create tracking context
contextRef.current = refine.events.trackSearch(query, response.results, {
surface: 'search_results',
totalResults: response.totalResults
});
} catch (err) {
setError(err as Error);
setResults([]);
setTotalResults(0);
} finally {
setIsLoading(false);
}
}, [options.topK, options.visualWeight, options.filters, options.sortBy]);
const trackClick = useCallback((productId: string, position: number) => {
contextRef.current?.trackClick(productId, position);
}, []);
const trackView = useCallback((productId: string, position: number) => {
contextRef.current?.trackView(productId, position);
}, []);
const trackAddToCart = useCallback((productId: string) => {
contextRef.current?.trackAddToCart(productId);
}, []);
return {
results,
totalResults,
isLoading,
error,
search,
trackClick,
trackView,
trackAddToCart
};
}
useRecommendations
// hooks/useRecommendations.ts
import { useState, useCallback, useEffect, useRef } from 'react';
import { refine } from '@/lib/refine';
import type { RecommendationResponse } from '@refine-ai/sdk';
interface UseRecommendationsResult {
recommendations: RecommendationResponse['results'];
isLoading: boolean;
error: Error | null;
trackClick: (productId: string, position: number) => void;
trackAddToCart: (productId: string) => void;
}
export function useSimilarItems(
anchorId: string,
topK: number = 8
): UseRecommendationsResult {
const [recommendations, setRecommendations] = useState<any[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
const contextRef = useRef<any>(null);
useEffect(() => {
if (!anchorId) return;
setIsLoading(true);
setError(null);
refine.recs.similarItems({ anchorId, topK })
.then(response => {
setRecommendations(response.results);
contextRef.current = refine.events.trackRecommendations(
response.serveId,
response.results,
'product_page',
'similar-items',
{ anchorId }
);
})
.catch(err => {
setError(err);
setRecommendations([]);
})
.finally(() => setIsLoading(false));
}, [anchorId, topK]);
const trackClick = useCallback((productId: string, position: number) => {
contextRef.current?.trackClick(productId, position);
}, []);
const trackAddToCart = useCallback((productId: string) => {
contextRef.current?.trackAddToCart(productId);
}, []);
return { recommendations, isLoading, error, trackClick, trackAddToCart };
}
export function useVisitorRecommendations(
configId: string,
topK: number = 12
): UseRecommendationsResult {
const [recommendations, setRecommendations] = useState<any[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
const contextRef = useRef<any>(null);
useEffect(() => {
refine.recs.forVisitor({ configId, topK })
.then(response => {
setRecommendations(response.results);
contextRef.current = refine.events.trackRecommendations(
response.serveId,
response.results,
'home_page',
'visitor-recs'
);
})
.catch(err => {
setError(err);
setRecommendations([]);
})
.finally(() => setIsLoading(false));
}, [configId, topK]);
const trackClick = useCallback((productId: string, position: number) => {
contextRef.current?.trackClick(productId, position);
}, []);
const trackAddToCart = useCallback((productId: string) => {
contextRef.current?.trackAddToCart(productId);
}, []);
return { recommendations, isLoading, error, trackClick, trackAddToCart };
}
useViewability
// hooks/useViewability.ts
import { useEffect, useRef } from 'react';
export function useViewability(
onView: (productId: string, position: number) => void,
threshold: number = 0.5,
duration: number = 1000
) {
const observerRef = useRef<IntersectionObserver | null>(null);
const viewedRef = useRef<Set<string>>(new Set());
const timersRef = useRef<Map<string, NodeJS.Timeout>>(new Map());
useEffect(() => {
observerRef.current = new IntersectionObserver(
(entries) => {
entries.forEach(entry => {
const el = entry.target as HTMLElement;
const productId = el.dataset.productId;
const position = parseInt(el.dataset.position || '0');
if (!productId || viewedRef.current.has(productId)) return;
if (entry.isIntersecting) {
const timer = setTimeout(() => {
if (viewedRef.current.has(productId)) return;
viewedRef.current.add(productId);
onView(productId, position);
}, duration);
timersRef.current.set(productId, timer);
} else {
const timer = timersRef.current.get(productId);
if (timer) {
clearTimeout(timer);
timersRef.current.delete(productId);
}
}
});
},
{ threshold }
);
return () => {
observerRef.current?.disconnect();
timersRef.current.forEach(clearTimeout);
};
}, [onView, threshold, duration]);
const observe = useCallback((element: HTMLElement | null) => {
if (element) {
observerRef.current?.observe(element);
}
}, []);
return { observe };
}
Component Patterns
Search Results Page
// components/SearchPage.tsx
import { useState, FormEvent } from 'react';
import { useSearch } from '@/hooks/useSearch';
import { ProductCard } from './ProductCard';
export function SearchPage() {
const [query, setQuery] = useState('');
const {
results,
totalResults,
isLoading,
error,
search,
trackClick,
trackView,
trackAddToCart
} = useSearch({ topK: 24 });
const handleSubmit = (e: FormEvent) => {
e.preventDefault();
search(query);
};
return (
<div className="search-page">
<form onSubmit={handleSubmit} className="search-form">
<input
type="search"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search products..."
/>
<button type="submit">Search</button>
</form>
{isLoading && <div className="loading">Searching...</div>}
{error && <div className="error">Search failed. Please try again.</div>}
{results.length > 0 && (
<>
<p className="results-count">
Showing {results.length} of {totalResults} results
</p>
<div className="product-grid">
{results.map((product, index) => (
<ProductCard
key={product.productId}
product={product}
position={index}
onView={() => trackView(product.productId, index)}
onClick={() => trackClick(product.productId, index)}
onAddToCart={() => trackAddToCart(product.productId)}
/>
))}
</div>
</>
)}
</div>
);
}
Product Card with Viewability
// components/ProductCard.tsx
import { useRef, useEffect } from 'react';
interface ProductCardProps {
product: {
productId: string;
title: string;
price: number;
imageUrl: string;
};
position: number;
onView: () => void;
onClick: () => void;
onAddToCart: () => void;
}
export function ProductCard({
product,
position,
onView,
onClick,
onAddToCart
}: ProductCardProps) {
const cardRef = useRef<HTMLDivElement>(null);
const hasViewed = useRef(false);
useEffect(() => {
const card = cardRef.current;
if (!card) return;
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting && !hasViewed.current) {
const timer = setTimeout(() => {
if (!hasViewed.current) {
hasViewed.current = true;
onView();
}
}, 1000);
return () => clearTimeout(timer);
}
},
{ threshold: 0.5 }
);
observer.observe(card);
return () => observer.disconnect();
}, [onView]);
const handleClick = () => {
onClick();
window.location.href = `/products/${product.productId}`;
};
const handleAddToCart = (e: React.MouseEvent) => {
e.stopPropagation();
onAddToCart();
// Add to cart logic
};
return (
<div
ref={cardRef}
className="product-card"
data-product-id={product.productId}
data-position={position}
onClick={handleClick}
>
<img src={product.imageUrl} alt={product.title} />
<h3>{product.title}</h3>
<p className="price">${product.price.toFixed(2)}</p>
<button onClick={handleAddToCart}>Add to Cart</button>
</div>
);
}
Similar Items Carousel
// components/SimilarItems.tsx
import { useSimilarItems } from '@/hooks/useRecommendations';
import { ProductCard } from './ProductCard';
interface SimilarItemsProps {
productId: string;
}
export function SimilarItems({ productId }: SimilarItemsProps) {
const {
recommendations,
isLoading,
error,
trackClick,
trackAddToCart
} = useSimilarItems(productId, 8);
if (isLoading) {
return <div className="loading">Loading recommendations...</div>;
}
if (error || recommendations.length === 0) {
return null;
}
return (
<section className="similar-items">
<h2>You May Also Like</h2>
<div className="carousel">
{recommendations.map((product, index) => (
<ProductCard
key={product.productId}
product={product}
position={index}
onView={() => {}}
onClick={() => trackClick(product.productId, index)}
onAddToCart={() => trackAddToCart(product.productId)}
/>
))}
</div>
</section>
);
}
Identity Management
Auth Integration
// hooks/useRefineAuth.ts
import { useEffect } from 'react';
import { refine } from '@/lib/refine';
import { useAuth } from '@/contexts/AuthContext';
export function useRefineAuth() {
const { user, isAuthenticated } = useAuth();
useEffect(() => {
if (isAuthenticated && user?.id) {
refine.identify(user.id);
}
}, [isAuthenticated, user?.id]);
const logout = () => {
refine.reset();
};
return { logout };
}
Provider with Auth
// app/layout.tsx
'use client';
import { RefineProvider } from '@/contexts/RefineContext';
import { AuthProvider } from '@/contexts/AuthContext';
import { useRefineAuth } from '@/hooks/useRefineAuth';
function RefineAuthSync({ children }) {
useRefineAuth();
return children;
}
export default function RootLayout({ children }) {
return (
<html>
<body>
<AuthProvider>
<RefineProvider>
<RefineAuthSync>
{children}
</RefineAuthSync>
</RefineProvider>
</AuthProvider>
</body>
</html>
);
}
Purchase Tracking
// hooks/usePurchaseTracking.ts
import { useCallback } from 'react';
import { refine } from '@/lib/refine';
interface OrderItem {
productId: string;
quantity: number;
price: number;
}
interface Order {
id: string;
total: number;
currency: string;
items: OrderItem[];
}
export function usePurchaseTracking() {
const trackPurchase = useCallback(async (order: Order) => {
refine.events.trackPurchase({
orderId: order.id,
value: order.total,
currency: order.currency,
items: order.items.map(item => ({
itemId: item.productId,
quantity: item.quantity,
unitPrice: item.price
}))
});
await refine.events.flush();
}, []);
return { trackPurchase };
}
Next Steps
Server-Side Usage
SSR and API patterns
Event Tracking
Complete event tracking guide