Context System
Pass custom data to components for dynamic template rendering.
Overview
Melony's context system allows you to pass custom data to MelonyMarkdown orMelonyWidget components. This data becomes available as variables in component templates, enabling dynamic content rendering based on your application state.
Basic Usage
Pass a context object to MelonyMarkdown:
import { MelonyMarkdown } from "melony";
function Chat() {
const context = {
user: {
name: "John Doe",
email: "john@example.com",
},
isAuthenticated: true,
};
const aiResponse = `
Welcome, {{user.name}}!
<card title="Profile">
<text value="Name: {{user.name}}" />
<text value="Email: {{user.email}}" />
</card>
`;
return <MelonyMarkdown context={context}>{aiResponse}</MelonyMarkdown>;
}Using Context Variables
Context variables can be used anywhere in component templates:
Hello {{user.name}}!
<card title="Welcome">
<text value="Email: {{user.email}}" />
<text value="Status: {{isAuthenticated ? 'Logged In' : 'Guest'}}" />
</card>Dynamic Data
Update context dynamically based on your application state:
import { useState } from "react";
import { MelonyMarkdown } from "melony";
function Dashboard() {
const [weather, setWeather] = useState({
temp: 72,
condition: "Sunny",
location: "San Francisco",
});
const context = {
weather,
currentTime: new Date().toLocaleTimeString(),
};
const content = `
<card title="Dashboard">
<text value="Time: {{currentTime}}" />
<text value="{{weather.location}} Weather" size="lg" weight="bold" />
<text value="{{weather.temp}}°F - {{weather.condition}}" />
</card>
`;
return (
<div>
<MelonyMarkdown context={context}>{content}</MelonyMarkdown>
<button onClick={() => setWeather({ ...weather, temp: weather.temp + 1 })}>
Increase Temp
</button>
</div>
);
}Nested Objects
Context supports deeply nested objects:
const context = {
app: {
name: "MyApp",
version: "1.0.0",
settings: {
theme: "dark",
language: "en",
},
},
user: {
profile: {
name: "Alice",
preferences: {
notifications: true,
newsletter: false,
},
},
},
};Access nested values with dot notation:
<card title="{{app.name}} v{{app.version}}">
<text value="Theme: {{app.settings.theme}}" />
<text value="User: {{user.profile.name}}" />
<text value="Notifications: {{user.profile.preferences.notifications ? 'On' : 'Off'}}" />
</card>Arrays in Context
Pass arrays and use them with the <for> component:
const context = {
tasks: [
{ id: 1, title: "Buy groceries", completed: false },
{ id: 2, title: "Walk dog", completed: true },
{ id: 3, title: "Code review", completed: false },
],
};
const content = `
<card title="My Tasks">
<for items="{{tasks}}">
<row gap="md">
<text value="{{item.title}}" flex="1" />
<badge
label="{{item.completed ? 'Done' : 'Pending'}}"
variant="{{item.completed ? 'success' : 'warning'}}"
/>
</row>
</for>
</card>
`;
<MelonyMarkdown context={context}>{content}</MelonyMarkdown>Functions in Context
You can pass functions to perform calculations or formatting:
const context = {
price: 99.99,
quantity: 3,
formatPrice: (num: number) => `$${num.toFixed(2)}`,
calculateTotal: (price: number, qty: number) => price * qty,
};
const content = `
<card title="Order Summary">
<text value="Price: {{formatPrice(price)}}" />
<text value="Quantity: {{quantity}}" />
<text value="Total: {{formatPrice(calculateTotal(price, quantity))}}" />
</card>
`;Real-World Example
Here's a complete example with authentication state:
import { MelonyProvider, MelonyMarkdown } from "melony";
import { useChat } from "ai/react";
import { useAuth } from "@/hooks/useAuth";
export default function Chat() {
const { messages } = useChat({ api: "/api/chat" });
const { user, isAuthenticated, logout } = useAuth();
const context = {
user: {
name: user?.name || "Guest",
email: user?.email || "",
avatar: user?.avatar || "",
},
isAuthenticated,
permissions: user?.permissions || [],
canEdit: user?.permissions?.includes("edit") || false,
canDelete: user?.permissions?.includes("delete") || false,
};
const handleAction = (action) => {
if (action.type === "logout") {
logout();
}
};
return (
<MelonyProvider onAction={handleAction}>
{messages.map((m) => (
<MelonyMarkdown key={m.id} context={context}>
{m.content}
</MelonyMarkdown>
))}
</MelonyProvider>
);
}The AI can now use context variables in its responses:
Welcome back, {{user.name}}!
<card title="Your Profile">
<text value="Email: {{user.email}}" />
<text value="Status: {{isAuthenticated ? 'Active' : 'Guest'}}" />
{{#if canEdit}}
<button
label="Edit Profile"
action='{"type":"edit-profile"}'
/>
{{/if}}
<button
label="Logout"
variant="outline"
action='{"type":"logout"}'
/>
</card>Context with Widgets
Context also works with MelonyWidget:
const widgetTemplate = `
<card title="{{user.name}}'s Dashboard">
<text value="Welcome back!" />
<text value="Last login: {{lastLogin}}" size="sm" color="muted" />
</card>
`;
const context = {
user: { name: "Alice" },
lastLogin: "2024-01-15 10:30 AM",
};
<MelonyWidget context={context}>{widgetTemplate}</MelonyWidget>Best Practices
- Keep it Flat: Avoid deeply nested structures when possible
- Type Safety: Define TypeScript interfaces for your context objects
- Minimal Data: Only pass data that templates actually need
- Immutability: Don't mutate context objects directly
- Memoization: Use useMemo for computed context values
- Security: Never pass sensitive data that shouldn't be exposed
TypeScript Support
Define types for your context objects:
interface UserContext {
user: {
name: string;
email: string;
avatar?: string;
};
isAuthenticated: boolean;
permissions: string[];
}
const context: UserContext = {
user: {
name: "Alice",
email: "alice@example.com",
},
isAuthenticated: true,
permissions: ["read", "write"],
};
<MelonyMarkdown context={context}>{content}</MelonyMarkdown>