React Integration
The @plucky-ai/react package provides React hooks for integrating the Plucky chat widget with proper lifecycle management. This is a companion to the Chat SDK.
Installation
npm install @plucky-ai/react @plucky-ai/chat-sdk
# or
yarn add @plucky-ai/react @plucky-ai/chat-sdk
# or
pnpm add @plucky-ai/react @plucky-ai/chat-sdk
Requirements
- React 18 or higher
@plucky-ai/chat-sdkas a peer dependency
useChat
The useChat hook initializes the Plucky chat widget in your React application.
Basic Usage
import { useChat } from '@plucky-ai/react'
function App() {
useChat({
appId: 'your-app-id',
baseUrl: 'https://widget.plucky.ai',
user: {
id: 'user-123',
name: 'John Doe',
email: '[email protected]',
},
})
return <div>Your app content</div>
}
With Dynamic User Data
import { useChat } from '@plucky-ai/react'
import { useAuth } from './your-auth-provider'
function App() {
const { user } = useAuth()
useChat({
appId: 'your-app-id',
baseUrl: 'https://widget.plucky.ai',
user: user
? {
id: user.id,
name: user.name,
email: user.email,
}
: undefined,
})
return <div>Your app content</div>
}
Parameters
The hook accepts the same options as the Plucky() function. See the SDK Reference for the complete list of configuration options.
useTools
The useTools hook registers tools with the AI assistant while properly managing React's component lifecycle. It automatically adds tools when the component mounts and removes them when it unmounts.
Why Use useTools?
When working with React, you should prefer useTools over calling addTools() directly because:
- Automatic cleanup - Tools are removed when the component unmounts
- Proper callback references - The
contextpattern ensures callbacks access current state - React lifecycle integration - Tools are re-registered when dependencies change
Basic Usage
import { useTools } from '@plucky-ai/react'
function OrderDashboard() {
useTools({
tools: [
{
name: 'get_recent_orders',
description: 'Get the most recent orders for the current user',
defaultLoadingText: 'Fetching orders...',
defaultSuccessText: 'Orders retrieved.',
cb: async () => {
const orders = await fetchRecentOrders()
return JSON.stringify(orders, null, 2)
},
},
],
})
return <div>Order Dashboard</div>
}
Using Context for React State
The context parameter solves a common React problem: accessing current state values inside callbacks. Without it, callbacks would capture stale values due to JavaScript closures.
import { useTools } from '@plucky-ai/react'
import { useState } from 'react'
function TemperatureWidget() {
const [temperature, setTemperature] = useState(72)
const [unit, setUnit] = useState<'celsius' | 'fahrenheit'>('fahrenheit')
useTools({
// Pass React state/props that tools need access to
context: {
temperature,
unit,
},
tools: [
{
name: 'get_current_temperature',
description: 'Get the current temperature reading',
cb: async (input, ctx) => {
// ctx contains the latest values of temperature and unit
return `Current temperature: ${ctx.temperature}°${ctx.unit === 'celsius' ? 'C' : 'F'}`
},
},
{
name: 'set_temperature',
description: 'Set the target temperature',
inputSchema: z.object({
value: z.number().describe('The temperature value to set'),
}),
cb: async (input, ctx) => {
// Note: You can't call setTemperature directly here
// Instead, return data and let the parent handle state updates
return {
content: `Temperature will be set to ${input.value}°${ctx.unit === 'celsius' ? 'C' : 'F'}`,
successText: `Set to ${input.value}°`,
}
},
},
],
})
return (
<div>
<p>
Temperature: {temperature}°{unit === 'celsius' ? 'C' : 'F'}
</p>
<button onClick={() => setTemperature((t) => t + 1)}>+</button>
<button onClick={() => setTemperature((t) => t - 1)}>-</button>
</div>
)
}
TypeScript Generics
For type-safe context access, use the generic parameter:
import { useTools } from '@plucky-ai/react'
interface MyContext {
userId: string
permissions: string[]
apiKey: string
}
function AdminPanel({ userId, permissions, apiKey }: MyContext) {
useTools<MyContext>({
context: {
userId,
permissions,
apiKey,
},
tools: [
{
name: 'admin_action',
description: 'Perform an admin action',
cb: async (input, ctx) => {
// ctx is typed as MyContext
if (!ctx.permissions.includes('admin')) {
return 'Permission denied'
}
// ...
return 'Action completed'
},
},
],
})
return <div>Admin Panel</div>
}
Tools with Input Schemas
import { useTools } from '@plucky-ai/react'
import { z } from 'zod'
const SearchSchema = z.object({
query: z.string().describe('The search query'),
limit: z.number().default(10).describe('Maximum results to return'),
category: z
.enum(['all', 'products', 'articles', 'users'])
.default('all')
.describe('Category to search in'),
})
function SearchableContent() {
useTools({
tools: [
{
name: 'search',
description: 'Search for content across the application',
inputSchema: SearchSchema,
defaultLoadingText: 'Searching...',
cb: async (input) => {
const { query, limit, category } = SearchSchema.parse(input)
const results = await performSearch(query, { limit, category })
return {
content: JSON.stringify(results, null, 2),
successText: `Found ${results.length} results`,
}
},
},
],
})
return <div>Searchable Content</div>
}
Common Patterns
Initializing in a Root Provider
Initialize the chat once at the root of your application:
// providers/PluckyProvider.tsx
import { useChat } from '@plucky-ai/react'
import { useAuth } from './AuthProvider'
export function PluckyProvider({ children }: { children: React.ReactNode }) {
const { user, isAuthenticated } = useAuth()
useChat({
appId: process.env.NEXT_PUBLIC_PLUCKY_APP_ID!,
baseUrl: process.env.NEXT_PUBLIC_PLUCKY_BASE_URL!,
user: isAuthenticated
? {
id: user.id,
name: user.name,
email: user.email,
metadata: {
role: user.role,
},
}
: undefined,
})
return <>{children}</>
}
// app/layout.tsx or _app.tsx
import { PluckyProvider } from './providers/PluckyProvider'
export default function RootLayout({ children }) {
return (
<AuthProvider>
<PluckyProvider>{children}</PluckyProvider>
</AuthProvider>
)
}
Conditional Tool Registration
Register tools only on specific pages or based on user permissions:
import { useTools } from '@plucky-ai/react'
import { useAuth } from './AuthProvider'
function AdminPage() {
const { user } = useAuth()
const isAdmin = user?.role === 'admin'
useTools({
context: { user },
tools: isAdmin
? [
{
name: 'delete_user',
description: 'Delete a user account (admin only)',
inputSchema: z.object({
userId: z.string(),
}),
cb: async (input, ctx) => {
await deleteUser(input.userId)
return `User ${input.userId} deleted`
},
},
]
: [],
})
return <div>Admin Page</div>
}
Route-Specific Tools
Register different tools based on the current route:
// pages/orders/[orderId].tsx
import { useTools } from '@plucky-ai/react'
import { useRouter } from 'next/router'
function OrderDetailPage() {
const router = useRouter()
const { orderId } = router.query
useTools({
context: { orderId: orderId as string },
tools: [
{
name: 'get_order_details',
description: 'Get details for the current order',
cb: async (_, ctx) => {
const order = await fetchOrder(ctx.orderId)
return JSON.stringify(order, null, 2)
},
},
{
name: 'cancel_order',
description: 'Cancel the current order',
cb: async (_, ctx) => {
await cancelOrder(ctx.orderId)
return {
content: `Order ${ctx.orderId} has been cancelled`,
successText: 'Order cancelled',
}
},
},
],
})
return <div>Order {orderId}</div>
}
Error Handling
Always handle errors gracefully in your tool callbacks:
useTools({
tools: [
{
name: 'risky_operation',
description: 'An operation that might fail',
cb: async (input) => {
try {
const result = await performRiskyOperation(input)
return `Success: ${result}`
} catch (error) {
// Return a helpful error message instead of throwing
if (error instanceof NotFoundError) {
return `Could not find the requested resource. Please check the ID and try again.`
}
if (error instanceof PermissionError) {
return `You don't have permission to perform this action.`
}
// Generic fallback
return `An error occurred: ${error.message}`
}
},
},
],
})
Next Steps
- Learn about Tool Calls in depth
- Explore the Chat SDK Reference for all available methods