Actions & Events
Handle user interactions in AI-generated components with Melony's built-in action system.
Overview
Melony provides a simple action system that lets you handle button clicks, form submissions, and other user interactions from AI-generated components. Actions are passed as JSON strings and dispatched through the onAction prop.
Setting Up Action Handler
Define an action handler function and pass it to MelonyProvider:
import { MelonyProvider, type Action } from "melony";
function Chat() {
const handleAction = (action: Action) => {
console.log("Action received:", action);
// Handle different action types
switch (action.type) {
case "refresh-weather":
handleWeatherRefresh(action.payload);
break;
case "submit-form":
handleFormSubmit(action.payload);
break;
default:
console.log("Unknown action type:", action.type);
}
};
return (
<MelonyProvider onAction={handleAction}>
{/* Your components */}
</MelonyProvider>
);
}Button Actions
The AI can attach actions to buttons using the action prop:
<button
label="Refresh Weather"
variant="primary"
action='{"type":"refresh-weather","location":"SF"}'
/>
<button
label="Delete Item"
variant="destructive"
action='{"type":"delete-item","id":"123"}'
/>When clicked, these buttons will trigger your onAction handler with the specified payload.
Action Type
Actions follow this TypeScript interface:
interface Action {
type: string; // Action identifier
payload?: any; // Optional action data
}Real-World Example
Here's a complete example with multiple action types:
"use client";
import { MelonyProvider, MelonyMarkdown } from "melony";
import { useChat } from "ai/react";
import { useState } from "react";
export default function Chat() {
const { messages, append } = useChat({ api: "/api/chat" });
const [loading, setLoading] = useState(false);
const handleAction = async (action) => {
switch (action.type) {
case "refresh-weather":
setLoading(true);
await append({
role: "user",
content: `Get weather for ${action.payload.location}`,
});
setLoading(false);
break;
case "send-email":
// Handle email sending
console.log("Sending email to:", action.payload.to);
break;
case "navigate":
// Handle navigation
window.location.href = action.payload.url;
break;
default:
console.log("Unknown action:", action);
}
};
return (
<MelonyProvider onAction={handleAction}>
<div className="space-y-4">
{messages.map((m) => (
<MelonyMarkdown key={m.id}>{m.content}</MelonyMarkdown>
))}
{loading && <p>Loading...</p>}
</div>
</MelonyProvider>
);
}Best Practices
- Type Safety: Use TypeScript unions for action types to ensure type safety
- Validation: Always validate action payloads before processing
- Error Handling: Wrap action handlers in try-catch blocks
- Loading States: Show loading indicators during async operations
- Feedback: Provide user feedback after action completion
TypeScript Example
Define action types for better type safety:
type WeatherAction = {
type: "refresh-weather";
payload: { location: string };
};
type EmailAction = {
type: "send-email";
payload: { to: string; subject: string };
};
type AppAction = WeatherAction | EmailAction;
const handleAction = (action: AppAction) => {
switch (action.type) {
case "refresh-weather":
// TypeScript knows payload has { location: string }
console.log(action.payload.location);
break;
case "send-email":
// TypeScript knows payload has { to: string; subject: string }
console.log(action.payload.to);
break;
}
};