React Integration

React Integration

Complete guide to using Produck SDK with React.

Overview

The React integration provides hooks, components, and context for seamless integration into React applications.

import {
  ProduckProvider,      // Context provider
  ProduckChat,          // Chat widget component
  ProduckTarget,        // Auto-scroll & highlight wrapper
  useProduckAction,     // Register action handlers
  useProduckMessages,   // Access chat state
  useProduck,          // Access full context
  useProduckReady,     // Check SDK status
} from '@produck/sdk/react';

ProduckProvider

Wrap your application with ProduckProvider to enable SDK functionality.

Basic Usage

app/layout.tsx
import { ProduckProvider } from '@produck/sdk/react';
 
export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <ProduckProvider
          config={{
            sdkKey: process.env.NEXT_PUBLIC_SDK_KEY,
          }}
        >
          {children}
        </ProduckProvider>
      </body>
    </html>
  );
}

Props

interface ProduckProviderProps {
  config: {
    sdkKey: string;          // Your SDK project key (required)
    apiUrl?: string;         // API endpoint (default: http://localhost:4001/api/v1)
  };
  children: ReactNode;       // Your app components
  onAction?: (key: string, payload: ActionPayload) => void;  // Global action callback
  onError?: (error: Error) => void;  // Global error handler
}

With Callbacks

<ProduckProvider
  config={{ sdkKey: 'your-key' }}
  onAction={(key, payload) => {
    // Track all actions in analytics
    analytics.track('Produck Action', { action: key });
  }}
  onError={(error) => {
    // Send errors to monitoring service
    Sentry.captureException(error);
  }}
>
  {children}
</ProduckProvider>

ProduckChat

Pre-built chat widget component.

Basic Usage

import { ProduckChat } from '@produck/sdk/react';
 
function App() {
  return (
    <div>
      <YourContent />
      <ProduckChat position="bottom-right" />
    </div>
  );
}

Props

interface ProduckChatProps {
  position?: 'bottom-right' | 'bottom-left' | 'inline';  // Chat position
  theme?: 'light' | 'dark';                               // Color theme
  primaryColor?: string;                                  // Accent color (hex)
  title?: string;                                         // Header title
  placeholder?: string;                                   // Input placeholder
  className?: string;                                     // Custom CSS class
  style?: React.CSSProperties;                           // Inline styles
}

Customization Examples

Custom Colors

<ProduckChat
  position="bottom-right"
  theme="dark"
  primaryColor="#8b5cf6"
  title="Ask me anything!"
  placeholder="Type your question..."
/>

Inline Chat

<div className="my-chat-container">
  <ProduckChat
    position="inline"
    className="w-full h-[500px]"
  />
</div>

Conditional Rendering

function App() {
  const [showChat, setShowChat] = useState(false);
  
  return (
    <>
      <button onClick={() => setShowChat(true)}>
        Open Support
      </button>
      
      {showChat && (
        <ProduckChat
          position="inline"
          onClose={() => setShowChat(false)}
        />
      )}
    </>
  );
}

useProduckAction

Register action handlers that respond to operations.

Basic Usage

import { useProduckAction } from '@produck/sdk/react';
 
function ContactButton() {
  const [isOpen, setIsOpen] = useState(false);
 
  useProduckAction('open-contact', () => {
    setIsOpen(true);
  });
 
  return (
    <>
      <button onClick={() => setIsOpen(true)}>Contact</button>
      {isOpen && <ContactModal onClose={() => setIsOpen(false)} />}
    </>
  );
}

With Payload

useProduckAction('show-product', (payload) => {
  console.log('Action triggered:', payload);
  // payload.actionKey = 'show-product'
  // payload.name = 'Show Product'
  // payload.responseMessage = '...'
  
  setProductId(payload.actionConfig.productId);
  setShowProduct(true);
});

Async Handlers

useProduckAction('submit-form', async (payload) => {
  setLoading(true);
  try {
    await api.submitForm(formData);
    showSuccess('Form submitted!');
  } catch (error) {
    showError('Failed to submit');
  } finally {
    setLoading(false);
  }
});

With Dependencies

function ProductDetail({ productId }) {
  const [quantity, setQuantity] = useState(1);
 
  // Handler re-registers when dependencies change
  useProduckAction('add-to-cart', () => {
    addToCart(productId, quantity);
  }, [productId, quantity]);
 
  return <div>...</div>;
}

Signature

function useProduckAction(
  actionKey: string,
  handler: (payload: ActionPayload) => void | Promise<void>,
  deps?: React.DependencyList
): void;

useProduckMessages

Access chat messages and send messages programmatically.

Basic Usage

import { useProduckMessages } from '@produck/sdk/react';
 
function CustomChat() {
  const { messages, isLoading, sendMessage } = useProduckMessages();
 
  return (
    <div>
      <MessageList messages={messages} />
      {isLoading && <LoadingIndicator />}
      <ChatInput onSend={sendMessage} />
    </div>
  );
}

Build Custom Chat UI

function CustomChatWidget() {
  const { messages, isLoading, sendMessage } = useProduckMessages();
  const [input, setInput] = useState('');
 
  const handleSend = async () => {
    if (!input.trim()) return;
    await sendMessage(input);
    setInput('');
  };
 
  return (
    <div className="chat-container">
      <div className="messages">
        {messages.map((msg, i) => (
          <div key={i} className={`message ${msg.role}`}>
            <p>{msg.content}</p>
            {msg.action && (
              <span className="action-badge">
                Action: {msg.action.name}
              </span>
            )}
          </div>
        ))}
      </div>
 
      {isLoading && <LoadingSpinner />}
 
      <div className="input-area">
        <input
          value={input}
          onChange={(e) => setInput(e.target.value)}
          onKeyPress={(e) => e.key === 'Enter' && handleSend()}
          placeholder="Ask something..."
        />
        <button onClick={handleSend}>Send</button>
      </div>
    </div>
  );
}

Return Type

interface UseProduckMessagesReturn {
  messages: ChatMessage[];
  isLoading: boolean;
  sendMessage: (message: string) => Promise<void>;
}
 
interface ChatMessage {
  role: 'user' | 'assistant';
  content: string;
  action?: ActionPayload;
}

useProduck

Access the full SDK context and instance.

Basic Usage

import { useProduck } from '@produck/sdk/react';
 
function SDKDebugger() {
  const {
    sdk,              // ProduckSDK instance
    isReady,          // Is SDK initialized?
    sessionToken,     // Current session token
    messages,         // Chat messages
    isLoading,        // Is message being sent?
    sendMessage,      // Send a message
    register,         // Register action handler
    unregister,       // Unregister action handler
  } = useProduck();
 
  return (
    <div>
      <p>Status: {isReady ? 'Ready' : 'Initializing...'}</p>
      <p>Session: {sessionToken}</p>
      <p>Messages: {messages.length}</p>
    </div>
  );
}

Advanced: Manual Registration

function AdvancedComponent() {
  const { register, unregister } = useProduck();
 
  useEffect(() => {
    const handler = (payload) => {
      console.log('Dynamic action!', payload);
    };
 
    register('dynamic-action', handler);
 
    return () => unregister('dynamic-action');
  }, [register, unregister]);
 
  return <div>...</div>;
}

Return Type

interface ProduckContextValue {
  sdk: ProduckSDK | null;
  isReady: boolean;
  sessionToken: string | null;
  messages: ChatMessage[];
  isLoading: boolean;
  sendMessage: (message: string) => Promise<void>;
  register: (key: string, handler: ActionHandler) => void;
  unregister: (key: string) => void;
}

useProduckReady

Check if SDK is initialized.

Basic Usage

import { useProduckReady } from '@produck/sdk/react';
 
function StatusIndicator() {
  const isReady = useProduckReady();
 
  if (!isReady) {
    return <div>Connecting to Produck...</div>;
  }
 
  return <div>✅ Connected</div>;
}

Conditional Rendering

function App() {
  const isReady = useProduckReady();
 
  return (
    <div>
      <Header />
      {isReady ? (
        <MainContent />
      ) : (
        <LoadingScreen message="Initializing AI assistant..." />
      )}
    </div>
  );
}

ProduckTarget

Wrap components to auto-scroll and highlight when triggered.

Basic Usage

import { ProduckTarget } from '@produck/sdk/react';
 
function PricingSection() {
  return (
    <ProduckTarget actionKey="show-pricing">
      <section id="pricing">
        <h2>Pricing</h2>
        <PricingCards />
      </section>
    </ProduckTarget>
  );
}

When user says "show me pricing", this section will:

  1. Scroll into view
  2. Highlight with animation
  3. Trigger custom callback (optional)

Props

interface ProduckTargetProps {
  actionKey: string;                    // Operation to respond to
  children: ReactNode;                  // Content to wrap
  onTrigger?: (payload: ActionPayload) => void;  // Custom callback
  scrollIntoView?: boolean;            // Auto-scroll (default: true)
  highlightDuration?: number;          // Highlight duration (ms, default: 3000)
  highlightStyle?: React.CSSProperties;  // Custom highlight styles
}

With Custom Callback

<ProduckTarget
  actionKey="show-features"
  onTrigger={(payload) => {
    console.log('User viewed features');
    analytics.track('Features Viewed', { source: 'ai' });
  }}
>
  <FeaturesSection />
</ProduckTarget>

Custom Styling

<ProduckTarget
  actionKey="show-testimonials"
  highlightStyle={{
    outline: '3px solid #f59e0b',
    backgroundColor: 'rgba(245, 158, 11, 0.1)',
    borderRadius: '8px',
  }}
  highlightDuration={5000}
>
  <TestimonialsSection />
</ProduckTarget>

Disable Auto-scroll

<ProduckTarget
  actionKey="highlight-banner"
  scrollIntoView={false}  // Only highlight, don't scroll
>
  <HeroBanner />
</ProduckTarget>

Patterns & Examples

Multi-Step Flows

function OnboardingFlow() {
  const [step, setStep] = useState(1);
 
  useProduckAction('start-onboarding', () => setStep(1));
  useProduckAction('next-step', () => setStep(s => s + 1));
  useProduckAction('previous-step', () => setStep(s => s - 1));
  useProduckAction('skip-onboarding', () => setStep(999));
 
  return <div>Step {step} content...</div>;
}

State Management

function ShoppingCart() {
  const [cart, setCart] = useCart();
 
  useProduckAction('add-to-cart', (payload) => {
    const productId = payload.actionConfig.productId;
    setCart(prev => [...prev, productId]);
  });
 
  useProduckAction('clear-cart', () => {
    setCart([]);
  });
 
  return <CartView items={cart} />;
}

Form Filling

function ContactForm() {
  const [formData, setFormData] = useState({});
 
  useProduckAction('fill-contact-form', (payload) => {
    setFormData({
      name: payload.actionConfig.name,
      email: payload.actionConfig.email,
      message: payload.actionConfig.message,
    });
  });
 
  return <form>...</form>;
}

Next Steps