Skip to main content

React Integration

This guide covers integrating Diosc into React applications, including hooks, context patterns, and Next.js specifics.

Basic Setup

Installation

npm install @diosc-ai/client

This gives you:

  • loadDiosc() — script injection + command-queue buffering
  • TypeScript types — fully-typed overloads for all diosc() commands
  • Window.diosc augmentation

Configure TypeScript for JSX

Add a one-line declaration so React recognises the <diosc-chat> web component. Place this in any .tsx file (e.g. your provider or App.tsx):

declare module 'react' {
namespace JSX {
interface IntrinsicElements {
'diosc-chat': Record<string, unknown>;
}
}
}

Basic Component

import { useEffect, useRef, useState } from 'react';
import { loadDiosc } from '@diosc-ai/client';

export function App() {
const ref = useRef(loadDiosc({
backendUrl: import.meta.env.VITE_DIOSC_URL,
apiKey: import.meta.env.VITE_DIOSC_API_KEY,
}));
const { diosc } = ref.current;
const [loaded, setLoaded] = useState(false);

useEffect(() => {
ref.current.ready
.then(() => setLoaded(true))
.catch(err => console.error('Failed to load Diosc:', err));
}, []);

useEffect(() => {
if (!loaded) return;

diosc('config', {
backendUrl: import.meta.env.VITE_DIOSC_URL,
apiKey: import.meta.env.VITE_DIOSC_API_KEY,
autoConnect: true,
});

return () => { diosc('disconnect'); };
}, [loaded]);

return (
<div className="app">
<h1>My Application</h1>
<diosc-chat />
</div>
);
}

React Hooks

useDiosc Hook

Create a reusable hook for Diosc functionality:

// hooks/useDiosc.ts
import { useEffect, useCallback, useState } from 'react';
import type { DioscFunction, DioscEvent, EventHandler } from '@diosc-ai/client';

export function useDioscEvents(diosc: DioscFunction) {
const [isConnected, setIsConnected] = useState(false);

useEffect(() => {
const unsubConnect = diosc('on', 'connected', () => setIsConnected(true));
const unsubDisconnect = diosc('on', 'disconnected', () => setIsConnected(false));

return () => {
unsubConnect();
unsubDisconnect();
};
}, [diosc]);

const sendMessage = useCallback((content: string) => {
diosc('invoke', content);
}, [diosc]);

return { isConnected, sendMessage };
}

useAuth Integration

Integrate with your existing auth:

// hooks/useDioscAuth.ts
import { useEffect } from 'react';
import { useAuth } from './useAuth'; // Your auth hook
import type { DioscFunction, AuthContext } from '@diosc-ai/client';

export function useDioscAuth(diosc: DioscFunction) {
const { accessToken, user, refreshToken } = useAuth();

useEffect(() => {
diosc('auth', async (): Promise<AuthContext> => {
let token = accessToken;
if (isTokenExpiring(accessToken)) {
token = await refreshToken();
}

return {
headers: { Authorization: `Bearer ${token}` },
userId: user?.id,
};
});
}, [diosc, accessToken, user, refreshToken]);
}

usePageContext Hook

Update page context on route changes:

// hooks/usePageContext.ts
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
import type { DioscFunction } from '@diosc-ai/client';

export function usePageContext(diosc: DioscFunction, pageData?: Record<string, unknown>) {
const location = useLocation();

useEffect(() => {
diosc('observe', 'navigation', (notify) => {
notify({
path: location.pathname,
search: location.search,
hash: location.hash,
});
});
}, [diosc, location, pageData]);
}

Context Provider Pattern

For app-wide configuration (this is the pattern used in the ACME Helpdesk sample):

// context/DioscProvider.tsx
import { type PropsWithChildren, useEffect, useState, useRef } from 'react';
import { loadDiosc } from '@diosc-ai/client';
import type { AuthContext, DioscConfig } from '@diosc-ai/client';

declare module 'react' {
namespace JSX {
interface IntrinsicElements {
'diosc-chat': Record<string, unknown>;
}
}
}

interface DioscProviderProps {
backendUrl: string;
apiKey?: string;
autoConnect?: boolean;
}

export function DioscProvider({
children,
backendUrl,
apiKey,
autoConnect = true,
}: PropsWithChildren<DioscProviderProps>) {
const ref = useRef(loadDiosc({ backendUrl, apiKey }));
const { diosc } = ref.current;
const [loaded, setLoaded] = useState(false);

// Wait for script to load
useEffect(() => {
if (window.__DIOSC_ASSISTANT_LOADED__) {
setLoaded(true);
return;
}
ref.current.ready
.then(() => setLoaded(true))
.catch(err => console.error('[Diosc] Load failed:', err));
}, []);

// Configure once loaded
useEffect(() => {
if (!loaded) return;

const config: DioscConfig = { backendUrl, autoConnect: false };
if (apiKey) config.apiKey = apiKey;

diosc('config', config);

// Set up auth (customize for your auth system)
diosc('auth', (): AuthContext => ({
headers: { Authorization: `Bearer ${getAccessToken()}` },
userId: getCurrentUserId(),
}));

if (autoConnect) diosc('connect');
}, [loaded, backendUrl, apiKey, autoConnect]);

return (
<>
{children}
<diosc-chat />
</>
);
}

Usage:

// App.tsx
function App() {
return (
<DioscProvider
backendUrl={import.meta.env.VITE_DIOSC_URL}
apiKey={import.meta.env.VITE_DIOSC_API_KEY}
>
<MainApp />
</DioscProvider>
);
}

Next.js Integration

Client-Only Rendering

Diosc requires browser APIs (window, document):

// components/DioscChat.tsx
'use client';

import dynamic from 'next/dynamic';
import { useEffect, useRef, useState } from 'react';
import { loadDiosc } from '@diosc-ai/client';

function DioscChatInner() {
const ref = useRef(loadDiosc({
backendUrl: process.env.NEXT_PUBLIC_DIOSC_URL!,
apiKey: process.env.NEXT_PUBLIC_DIOSC_API_KEY!,
}));
const [loaded, setLoaded] = useState(false);

useEffect(() => {
ref.current.ready.then(() => setLoaded(true));
}, []);

useEffect(() => {
if (!loaded) return;
ref.current.diosc('config', {
backendUrl: process.env.NEXT_PUBLIC_DIOSC_URL,
apiKey: process.env.NEXT_PUBLIC_DIOSC_API_KEY,
autoConnect: true,
});
}, [loaded]);

return <diosc-chat />;
}

// Disable SSR
export const DioscChat = dynamic(
() => Promise.resolve(DioscChatInner),
{ ssr: false }
);

App Router (Next.js 13+)

// app/layout.tsx
import { DioscChat } from '@/components/DioscChat';

export default function RootLayout({ children }) {
return (
<html>
<body>
{children}
<DioscChat />
</body>
</html>
);
}

Pages Router

// pages/_app.tsx
import type { AppProps } from 'next/app';
import dynamic from 'next/dynamic';

const DioscChat = dynamic(
() => import('@/components/DioscChat'),
{ ssr: false }
);

export default function App({ Component, pageProps }: AppProps) {
return (
<>
<Component {...pageProps} />
<DioscChat />
</>
);
}

Event Handling

Global Event Listeners

useEffect(() => {
const unsubscribers = [
diosc('on', 'message', handleMessage),
diosc('on', 'tool_execution_start', handleToolStart),
diosc('on', 'tool_execution_complete', handleToolEnd),
diosc('on', 'error', handleError),
diosc('on', 'approval_required', handleApproval),
];

return () => {
unsubscribers.forEach(unsub => unsub());
};
}, []);

Custom Tool Registration

Register client-side tools that the AI can invoke:

useEffect(() => {
if (!loaded) return;

diosc('tool', 'navigate', async (params) => {
const path = (params.path || params.url) as string;
if (path.startsWith('http')) {
window.open(path, '_blank');
} else {
navigate(path); // react-router navigate
}
return { success: true, data: { navigatedTo: path } };
});

diosc('tool', 'showNotification', async (params) => {
toast(params.message as string);
return { success: true };
});
}, [loaded, navigate]);

Troubleshooting

loadDiosc and React Strict Mode

React 18+ Strict Mode mounts components twice in development. loadDiosc() is idempotent — the script is injected at most once:

// Safe in Strict Mode
const ref = useRef(loadDiosc({ backendUrl, apiKey }));

TypeScript: Unknown <diosc-chat> Element

Add the JSX declaration shown in Configure TypeScript for JSX.

State Updates After Unmount

useEffect(() => {
let mounted = true;

const unsub = diosc('on', 'message', (msg) => {
if (mounted) setMessages(prev => [...prev, msg]);
});

return () => {
mounted = false;
unsub();
};
}, []);

Next Steps