React/Next.js Integration

Learn how to integrate Chatoshi SDK with React and Next.js applications for seamless crypto assistant functionality.

React/Next.js Integration

This guide shows you how to integrate the Chatoshi SDK with React and Next.js applications. Since the SDK uses script tags, we'll use the useEffect hook to properly manage the SDK lifecycle.

Basic React Integration

Method 1: Using useEffect with Script Loading

import React, { useEffect, useRef } from 'react';

const CryptoAssistantWidget = ({ 
  partnerKey, 
  mode = 'default' 
}) => {
  const chatRef = useRef(null);
  const containerRef = useRef(null);
  const sdkLoadedRef = useRef(false);

  useEffect(() => {
    const loadChatoshiSDK = async () => {
      // Check if SDK is already loaded
      if (window.Chatoshi) {
        initializeChat();
        return;
      }

      // Load SDK script
      const script = document.createElement('script');
      script.src = '/chatoshi-sdk.js'; // Make sure this file is in your public folder
      script.async = true;
      
      script.onload = () => {
        sdkLoadedRef.current = true;
        initializeChat();
      };

      script.onerror = () => {
        console.error('Failed to load Chatoshi SDK');
      };

      document.head.appendChild(script);

      // Cleanup function
      return () => {
        if (script.parentNode) {
          script.parentNode.removeChild(script);
        }
      };
    };

    const initializeChat = () => {
      if (!window.Chatoshi) return;

      // Destroy existing chat instance
      if (chatRef.current) {
        chatRef.current.destroy();
      }

      const options = {
        partnerKey,
        mode,
      };

      // Add container for default mode
      if (mode === 'default' && containerRef.current) {
        options.container = containerRef.current;
      }

      // Initialize Chatoshi
      chatRef.current = new window.Chatoshi(options);

      // Event listeners
      chatRef.current.on('app:ready', () => {
        console.log('Chatoshi crypto assistant is ready in React!');
      });

      chatRef.current.on('message:sent', (data) => {
        console.log('Crypto query sent:', data);
      });

      chatRef.current.on('message:received', (data) => {
        console.log('AI response received:', data);
      });
    };

    loadChatoshiSDK();

    // Cleanup on unmount
    return () => {
      if (chatRef.current) {
        chatRef.current.destroy();
        chatRef.current = null;
      }
    };
  }, [partnerKey, mode]);

  return (
    <div>
      {mode === 'default' && (
        <div 
          ref={containerRef}
          style={{ 
            width: '100%', 
            height: '500px',
            border: '1px solid #ddd',
            borderRadius: '8px'
          }}
        />
      )}
    </div>
  );
};

export default CryptoAssistantWidget;

Usage in React Component

import React from 'react';
import CryptoAssistantWidget from './components/CryptoAssistantWidget';

function App() {
  return (
    <div className="App">
      <h1>My Crypto Trading App</h1>
      
      {/* Inline Crypto Assistant */}
      <div style={{ margin: '20px 0' }}>
        <h2>Crypto Analysis Assistant</h2>
        <CryptoAssistantWidget 
          partnerKey="your-partner-key"
          mode="default"
        />
      </div>

      {/* Popup Crypto Assistant */}
      <div style={{ margin: '20px 0' }}>
        <h2>Quick Crypto Help</h2>
        <CryptoAssistantWidget 
          partnerKey="your-partner-key"
          mode="popup"
        />
      </div>
    </div>
  );
}

export default App;

Next.js Integration

Method 1: Using Next.js Script Component

// pages/index.js or app/page.js
import { useEffect, useRef, useState } from 'react';
import Script from 'next/script';

export default function HomePage() {
  const chatRef = useRef(null);
  const [sdkLoaded, setSdkLoaded] = useState(false);

  const initializeChat = () => {
    if (!window.Chatoshi || chatRef.current) return;

    chatRef.current = new window.Chatoshi({
      partnerKey: 'your-partner-key',
      mode: 'popup',
      popupOptions: {
        width: '400px',
        height: '600px',
        position: 'bottom-right'
      }
    });

    chatRef.current.on('app:ready', () => {
      console.log('Chatoshi crypto assistant ready in Next.js!');
    });
  };

  useEffect(() => {
    if (sdkLoaded) {
      initializeChat();
    }

    return () => {
      if (chatRef.current) {
        chatRef.current.destroy();
        chatRef.current = null;
      }
    };
  }, [sdkLoaded]);

  return (
    <>
      <Script
        src="/chatoshi-sdk.js"
        strategy="afterInteractive"
        onLoad={() => setSdkLoaded(true)}
        onError={() => console.error('Failed to load Chatoshi SDK')}
      />
      
      <div>
        <h1>My Crypto Trading App</h1>
        <p>Crypto assistant will appear as a popup!</p>
      </div>
    </>
  );
}

Method 2: Custom Hook for Chatoshi

Create a reusable hook:

// hooks/useChatoshi.js
import { useEffect, useRef, useState } from 'react';

export const useChatoshi = (options) => {
  const chatRef = useRef(null);
  const [isReady, setIsReady] = useState(false);
  const [isLoading, setIsLoading] = useState(false);

  useEffect(() => {
    const loadAndInitialize = async () => {
      setIsLoading(true);

      // Load SDK if not already loaded
      if (!window.Chatoshi) {
        await loadSDK();
      }

      // Initialize chat
      if (chatRef.current) {
        chatRef.current.destroy();
      }

      chatRef.current = new window.Chatoshi(options);

      chatRef.current.on('app:ready', () => {
        setIsReady(true);
        setIsLoading(false);
      });

      chatRef.current.on('app:error', (error) => {
        console.error('Chatoshi error:', error);
        setIsLoading(false);
      });
    };

    const loadSDK = () => {
      return new Promise((resolve, reject) => {
        const script = document.createElement('script');
        script.src = '/chatoshi-sdk.js';
        script.onload = resolve;
        script.onerror = reject;
        document.head.appendChild(script);
      });
    };

    loadAndInitialize();

    // Cleanup
    return () => {
      if (chatRef.current) {
        chatRef.current.destroy();
        chatRef.current = null;
      }
      setIsReady(false);
    };
  }, [options.partnerKey, options.mode]);

  const openChat = () => {
    if (chatRef.current?.open) {
      chatRef.current.open();
    }
  };

  const closeChat = () => {
    if (chatRef.current?.close) {
      chatRef.current.close();
    }
  };

  const toggleChat = () => {
    if (chatRef.current?.toggle) {
      chatRef.current.toggle();
    }
  };

  return {
    isReady,
    isLoading,
    openChat,
    closeChat,
    toggleChat,
    chatInstance: chatRef.current
  };
};

Using the Custom Hook

// components/ChatComponent.js
import { useChatoshi } from '../hooks/useChatoshi';

const ChatComponent = () => {
  const { isReady, isLoading, openChat, closeChat } = useChatoshi({
    partnerKey: 'your-partner-key',
    mode: 'drawer',
    drawerOptions: {
      width: '400px',
      position: 'right'
    }
  });

  return (
    <div>
      <h2>Chat Controls</h2>
      {isLoading && <p>Loading chat...</p>}
      {isReady && (
        <div>
          <button onClick={openChat}>Open Chat</button>
          <button onClick={closeChat}>Close Chat</button>
          <p>✅ Chat is ready!</p>
        </div>
      )}
    </div>
  );
};

export default ChatComponent;

Advanced Next.js Integration

App Router (Next.js 13+)

// app/components/ChatProvider.js
'use client';

import { createContext, useContext, useEffect, useRef, useState } from 'react';

const ChatContext = createContext();

export const useChatContext = () => {
  const context = useContext(ChatContext);
  if (!context) {
    throw new Error('useChatContext must be used within ChatProvider');
  }
  return context;
};

export const ChatProvider = ({ children, config }) => {
  const chatRef = useRef(null);
  const [isReady, setIsReady] = useState(false);

  useEffect(() => {
    const initChat = async () => {
      // Load SDK
      if (!window.Chatoshi) {
        const script = document.createElement('script');
        script.src = '/chatoshi-sdk.js';
        await new Promise((resolve) => {
          script.onload = resolve;
          document.head.appendChild(script);
        });
      }

      // Initialize
      chatRef.current = new window.Chatoshi(config);
      
      chatRef.current.on('app:ready', () => {
        setIsReady(true);
      });
    };

    initChat();

    return () => {
      if (chatRef.current) {
        chatRef.current.destroy();
      }
    };
  }, [config]);

  return (
    <ChatContext.Provider value={{ 
      chat: chatRef.current, 
      isReady 
    }}>
      {children}
    </ChatContext.Provider>
  );
};

Layout Integration

// app/layout.js
import { ChatProvider } from './components/ChatProvider';

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>
        <ChatProvider 
          config={{
            partnerKey: process.env.NEXT_PUBLIC_CHATOSHI_PARTNER_KEY,
            mode: 'popup'
          }}
        >
          {children}
        </ChatProvider>
      </body>
    </html>
  );
}

Environment Variables

Create a .env.local file in your Next.js project:

# .env.local
NEXT_PUBLIC_CHATOSHI_PARTNER_ID=your-partner-id
NEXT_PUBLIC_CHATOSHI_PARTNER_SECRET=your-partner-secret
NEXT_PUBLIC_CHATOSHI_APP_ID=your-app-id

TypeScript Support

// types/chatoshi.d.ts
declare global {
  interface Window {
    Chatoshi: any;
  }
}

// Hook with TypeScript
interface ChatoshiOptions {
  partnerKey: string;
  mode?: 'default' | 'popup' | 'drawer' | 'full';
  container?: string | HTMLElement;
  theme?: 'light' | 'dark' | 'system';
  popupOptions?: {
    width?: string;
    height?: string;
    position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
  };
  drawerOptions?: {
    width?: string;
    position?: 'left' | 'right';
  };
}

export const useChatoshi = (options: ChatoshiOptions) => {
  // Hook implementation...
};

Best Practices

  1. Always cleanup: Use cleanup functions to destroy chat instances
  2. Check for SDK: Always verify window.Chatoshi exists before initializing
  3. Environment variables: Use environment variables for credentials
  4. Error handling: Implement proper error handling for SDK loading failures
  5. Loading states: Show loading indicators while SDK initializes
  6. SSR considerations: Remember that window object doesn't exist during SSR

Troubleshooting

Common Issues

"Chatoshi is not defined" error

  • Make sure the SDK script is loaded before initialization
  • Use proper loading state management

Multiple instances created

  • Always destroy previous instances before creating new ones
  • Use refs to maintain single instance per component

SSR/Hydration issues

  • Use useEffect for client-side only initialization
  • Check for window object existence

This integration approach ensures proper lifecycle management and works seamlessly with React and Next.js applications.