showcase/shell-docs/src/content/reference/react-native/hooks/useFrontendTool.mdx
useFrontendTool registers a client-side tool with CopilotKit at component scope. When the agent decides to call the tool, the provided handler function executes on the device. Optionally, you can supply a render component to display custom UI in the chat showing the tool's execution progress and results.
The hook manages the full registration lifecycle: it warns if a tool with the same name already exists, registers the tool and its render component on mount, and cleans up both registrations on unmount. In v2, parameter schemas are defined using Zod instead of plain parameter arrays.
import { useFrontendTool } from "@copilotkit/react-native";
function useFrontendTool<T extends Record<string, unknown>>(
tool: ReactFrontendTool<T>,
deps?: ReadonlyArray<unknown>,
): void;
<PropertyReference name="handler" type="(args: T, context?: { toolCall, agent, signal? }) => Promise<string>" required
An async function that executes when the agent calls the tool. Receives the
validated, typed arguments and an optional context object containing
toolCall (the raw tool call metadata), agent (the agent instance that
invoked the tool), and signal (an AbortSignal that is aborted when the
user stops the agent via stopAgent() or agent.abortRun()). Long-running
handlers can check signal.aborted to exit early.
</PropertyReference>
<PropertyReference name="render" type="React.ComponentType<{ name: string; args: Partial<T>; status: ToolCallStatus; result: string | undefined }>"
An optional component rendered in the chat interface to visualize tool
execution. In React Native the render component returns React Native elements
(e.g. <View> / <Text>). The component receives name (the tool name),
args (the arguments, partial while streaming and complete once execution
starts), status (one of ToolCallStatus.InProgress,
ToolCallStatus.Executing, or ToolCallStatus.Complete), and result (the
string result returned by the handler, available only when status is
Complete).
</PropertyReference>
import { useState } from "react";
import { Text, View } from "react-native";
import { useFrontendTool } from "@copilotkit/react-native";
import { z } from "zod";
function TodoManager() {
const [todos, setTodos] = useState<string[]>([]);
useFrontendTool(
{
name: "addTodo",
description: "Add a new item to the user's todo list",
parameters: z.object({
text: z.string().describe("The todo item text"),
priority: z.enum(["low", "medium", "high"]).describe("Priority level"),
}),
handler: async ({ text, priority }) => {
setTodos((prev) => [...prev, text]);
return `Added "${text}" with ${priority} priority`;
},
},
[],
);
return (
<View>
{todos.map((t, i) => (
<Text key={i}>{t}</Text>
))}
</View>
);
}
import { Text, View } from "react-native";
import { useFrontendTool, ToolCallStatus } from "@copilotkit/react-native";
import { z } from "zod";
function WeatherWidget() {
useFrontendTool(
{
name: "getWeather",
description: "Fetch and display weather information for a city",
parameters: z.object({
city: z.string().describe("City name"),
units: z.enum(["celsius", "fahrenheit"]).default("celsius"),
}),
handler: async ({ city, units }, { signal }) => {
const response = await fetch(
`https://your-server/api/weather?city=${city}&units=${units}`,
{ signal },
);
const data = await response.json();
return JSON.stringify(data);
},
render: ({ args, status, result }) => {
if (status === ToolCallStatus.InProgress) {
return (
<View>
<Text>Fetching weather for {args.city}...</Text>
</View>
);
}
if (status === ToolCallStatus.Complete && result) {
const data = JSON.parse(result);
return (
<View>
<Text>{data.city}</Text>
<Text>
{data.temperature}° {data.units}
</Text>
<Text>{data.conditions}</Text>
</View>
);
}
return null;
},
},
[],
);
return null;
}
import { View } from "react-native";
import { useFrontendTool } from "@copilotkit/react-native";
import { z } from "zod";
function AdminPanel({ isAdmin }: { isAdmin: boolean }) {
useFrontendTool(
{
name: "deleteUser",
description: "Delete a user account by ID (admin only)",
parameters: z.object({
userId: z.string().describe("The ID of the user to delete"),
}),
handler: async ({ userId }) => {
await fetch(`https://your-server/api/users/${userId}`, {
method: "DELETE",
});
return `User ${userId} deleted`;
},
available: isAdmin ? "enabled" : "disabled",
},
[isAdmin],
);
return <View></View>;
}
name is already registered, the hook logs a warning. Only one tool per name is active at a time.deps is provided, the tool registration is refreshed whenever any dependency value changes, similar to useEffect.render function is provided, it is added to the internal render tool calls registry. It receives streaming args (partial during InProgress, complete during Executing and Complete).void.useRenderTool: register renderer-only tool call UI (named or wildcard)useCopilotKit: access the core instance and run tools programmatically