useRenderToolCall

React hook that returns a renderer function for tool calls in the chat interface


Overview

Same name, different API than v1

The v2 useRenderToolCall (this page, from @copilotkit/react-core/v2) is a no-argument hook that returns a renderer function for consuming tool calls. It is not the v1 useRenderToolCall, which takes a { name, parameters, render } config object to register a renderer. To register renderers in v2, use useFrontendTool or useHumanInTheLoop.

useRenderToolCall returns a renderer function that maps tool calls to React elements. The returned function looks up the matching render configuration by tool name (preferring a renderer scoped to the active agentId, then an unscoped one, then a wildcard "*" renderer if no exact match is found), partially parses the JSON arguments, determines the current ToolCallStatus, and returns the appropriate React element. When neither a per-tool nor a wildcard renderer is registered, it falls back to the framework's built-in DefaultToolCallRenderer, so the returned element is effectively always non-null (the signature keeps ReactElement | null for type compatibility).

This hook is primarily used by chat UI components to display visual feedback for tool calls. In most applications you will not call it directly; instead, you register render components via useFrontendTool or useHumanInTheLoop, and the chat components use useRenderToolCall internally to resolve them.

Signature

import { useRenderToolCall } from "@copilotkit/react-core/v2";

function useRenderToolCall(): (
  props: UseRenderToolCallProps,
) => React.ReactElement | null;

Return Value

Prop

Type

Status Resolution

The renderer determines the ToolCallStatus based on the inputs:

ConditionStatusresult
No toolMessage, tool call id not in the executing setToolCallStatus.InProgressundefined
No toolMessage, tool call id is in the provider executing setToolCallStatus.Executingundefined
A toolMessage is presentToolCallStatus.CompleteThe result string from the tool message

ToolCallStatus

The ToolCallStatus enum is exported from @copilotkit/react-core/v2:

ValueDescription
ToolCallStatus.InProgressThe tool call's arguments are still being streamed.
ToolCallStatus.ExecutingArguments are fully resolved; the tool is executing.
ToolCallStatus.CompleteExecution is finished and a result is available.

Usage

Using the Renderer in a Custom Chat Component

function CustomChatMessage({ message }) {
  const renderToolCall = useRenderToolCall();

  if (message.type === "tool_call") {
    // Always returns an element: your registered renderer, the wildcard
    // ("*") renderer, or the framework's built-in DefaultToolCallRenderer.
    const element = renderToolCall({
      toolCall: message.toolCall,
      toolMessage: message.toolMessage,
    });

    return <div className="tool-call-container">{element}</div>;
  }

  return <div>{message.content}</div>;
}

Registering Renderers with useFrontendTool

Tool call renderers are typically registered through useFrontendTool or directly via the renderToolCalls prop on the provider. The useRenderToolCall hook resolves these registrations at render time.

function App() {
  // Register a tool with a render component
  useFrontendTool(
    {
      name: "searchDatabase",
      description: "Search the product database",
      parameters: z.object({
        query: z.string().describe("Search query"),
      }),
      handler: async ({ query }) => {
        const results = await fetch(`/api/search?q=${query}`);
        return JSON.stringify(await results.json());
      },
      render: ({ args, status, result }) => {
        if (status === ToolCallStatus.InProgress) {
          return <div>Searching for "{args.query}"...</div>;
        }
        if (status === ToolCallStatus.Complete && result) {
          const data = JSON.parse(result);
          return (
            <div>
              <p>
                Found {data.length} results for "{args.query}"
              </p>
              <ul>
                {data.map((item: any) => (
                  <li key={item.id}>{item.name}</li>
                ))}
              </ul>
            </div>
          );
        }
        return null;
      },
    },
    [],
  );

  return <ChatInterface />;
}

Wildcard Renderer

If no exact name match is found, the hook falls back to a wildcard "*" renderer. This is useful for providing a generic UI for all unhandled tool calls.

function App() {
  return (
    <CopilotKit
      runtimeUrl="/api/copilotkit"
      renderToolCalls={[
        {
          name: "*",
          render: ({ name, args, status, result }) => {
            if (status === ToolCallStatus.InProgress) {
              return (
                <div className="text-gray-500 text-sm">Running {name}...</div>
              );
            }
            if (status === ToolCallStatus.Complete) {
              return (
                <div className="text-green-600 text-sm">{name} completed.</div>
              );
            }
            return null;
          },
        },
      ]}
    >
      <YourApp />
    </CopilotKit>
  );
}

Behavior

  • Name-based lookup with agent scoping: The renderer searches registered render configurations for an exact tool name match first. When multiple match the same name, it prefers the one scoped to the active agentId, then an unscoped renderer, then the first match. If no exact match exists, it falls back to a wildcard ("*") renderer, and finally to the framework's built-in DefaultToolCallRenderer.
  • Partial JSON parsing: The tool call's arguments string is parsed with partialJSONParse before being passed to the render component, so partially-streamed arguments can render incrementally while the tool call is still InProgress.
  • Status inference: Status is determined from the presence of the toolMessage prop and whether the tool call id is in the provider's executing set. The render component always receives a consistent shape with name, toolCallId, args, status, and result.
  • Default renderer for unmatched tools: If no renderer is registered for the tool name and no wildcard renderer exists, the function falls back to the built-in DefaultToolCallRenderer rather than returning null, so unhandled tool calls still paint out-of-the-box.
  • Used internally by chat components: The built-in CopilotChat, CopilotPopup, and CopilotSidebar components use this hook to render tool calls. You only need to call it directly when building custom chat UIs.

Disable default tool rendering

Because the hook falls back to the built-in DefaultToolCallRenderer when no renderer matches, tool calls paint UI out-of-the-box. To turn that off, register a renderer that produces no UI (an empty fragment, () => <></>). A registered renderer takes priority over the default, so no fallback UI is shown.

Register a wildcard ("*") renderer with useRenderTool to disable the default for all tool calls. The wildcard overload takes no parameters schema, so no Zod is required. Any tool with its own named renderer still paints.

function App() {
  // Suppress the built-in DefaultToolCallRenderer for every tool call
  // without its own named renderer.
  useRenderTool({ name: "*", render: () => <></> }, []);

  return <YourApp />;
}

For specific tools

To disable rendering for a single tool, register a renderer under that tool's name. Named renderers take a parameters schema; when you only want to suppress UI, pass a pass-through schema (z.any()):

import { z } from "zod";

function App() {
  useRenderTool(
    { name: "specificTool", parameters: z.any(), render: () => <></> },
    [],
  );

  return <YourApp />;
}

You can also opt out conditionally — render nothing for some statuses and real UI for others (for example, hide while InProgress but show a result on Complete), since the render function receives the current status.

Related

  • useRenderTool -- register a renderer by tool name or wildcard (no schema needed for "*")
  • useFrontendTool -- register tools with render components
  • useHumanInTheLoop -- register interactive tools with render components
  • CopilotKit -- configure static render tool calls at the provider level