Files
Maxim 95c9c0a5a0 docs(react-native): make the render-tool docs true of the shim, not of the deletion
The deprecated `useRenderTool` shim landed after these pages were written, so
they described a harder break than the code delivers — "React Native has no
render-tool API of its own", and a migration table presenting the old hook as
simply gone.

reference/react-native/hooks/useRenderTool:

- The overview and the migration narrative now say what actually happens: the
  name still exists, still works this release, routes by shape, warns, and goes
  away in the next minor. The routing table is the shim's three rules verbatim
  (src/hooks/useRenderTool.ts `routeFor`), including the unconditional wildcard
  and why it is unconditional.
- The warning is quoted from the source string rather than paraphrased, and gets
  its own "What the warning does not cover" section: dev-only gate, per-name
  dedup for the module's lifetime, silent when no old field is present, and
  emitted from an effect so an unmounted screen never warns.
- "Three shapes the compiler does not catch" is kept, not deleted. It is now
  framed as what the shim exists to route — and as what to audit by hand anyway,
  because the notice is development-only and because all three go back to
  degrading silently once the shim is removed.
- Two new migration rows for losses the table omitted: bare `RenderToolProps`
  is now `TS2314` (core's `S` has no default where RN's `T` did), and the
  `args` -> `parameters` render-prop rename is still `TS2339` even through the
  shim — verified with tsc, the shim restores the old CONFIG fields only. The
  `RenderToolFunction<T>` row now points somewhere instead of saying "gone".
- `parameters` was documented as `Partial<T> | T`, using a generic this page
  renamed to `S` and explicitly warns is the schema rather than the arguments.
  It is `Partial<InferSchemaOutput<S>> | InferSchemaOutput<S>`
  (react-core/src/v2/hooks/use-render-tool.tsx:9-31).

reference/react-native/hooks/useFrontendTool had zero mentions of `ReactElement`,
`ReactNode` or `FlatList` despite being where the migration sends people. It now
documents that its `render` is a `React.ComponentType` and therefore accepts a
bare string that throws on a device, and points at the opt-in
`FrontendToolRenderFunction<T>`.

docs/frontends/react-native and packages/react-native/USAGE.md (which ships in
the npm tarball — no `files` array, no `.npmignore`) get the same corrections at
their own length.

Every code sample added here was typechecked by pasting it into the package and
running `tsc --noEmit`, including the ones asserted to FAIL. Internal links and
heading anchors were checked mechanically against the content tree.
2026-09-09 19:32:45 +02:00

6.9 KiB

@copilotkit/react-native — Usage

Prerequisites

Install all required peer dependencies:

npm install react react-native @gorhom/bottom-sheet react-native-gesture-handler react-native-reanimated react-native-streamdown

@gorhom/bottom-sheet, react-native-gesture-handler, react-native-reanimated, and react-native-streamdown are required peer dependencies for the UI components.

Quick Start

import "@copilotkit/react-native/polyfills";
import {
  CopilotKitProvider,
  CopilotChat,
  useFrontendTool,
} from "@copilotkit/react-native";
import { z } from "zod";

function App() {
  return (
    <CopilotKitProvider runtimeUrl="https://your-server/api/copilotkit">
      <ChatScreen />
    </CopilotKitProvider>
  );
}

function ChatScreen() {
  // parameters accepts any StandardSchemaV1-compatible schema (Zod, Valibot, ArkType, etc.)
  useFrontendTool({
    name: "showWeather",
    description: "Show weather info",
    parameters: z.object({ city: z.string() }),
    render: ({ args }) => <WeatherCard city={args.city} />,
  });

  return <CopilotChat placeholder="Ask anything..." />;
}

Available Components

CopilotChat

Inline chat panel. Renders a message list with an input bar.

import { CopilotChat } from "@copilotkit/react-native";

<CopilotChat placeholder="Type a message..." />;

CopilotModal

Modal chat overlay. Open/close programmatically via a ref.

import { CopilotModal, type CopilotModalRef } from "@copilotkit/react-native";
import { useRef } from "react";

const modalRef = useRef<CopilotModalRef>(null);

<CopilotModal ref={modalRef} headerTitle="Assistant" />;

// Open it:
modalRef.current?.open();

CopilotMarkdown

Renders Markdown text with sensible React Native styling.

import { CopilotMarkdown } from "@copilotkit/react-native";

<CopilotMarkdown content="**Hello** from CopilotKit!" />;

AssistantMessage / UserMessage

Individual message bubbles. Useful when building a custom chat UI.

import { AssistantMessage, UserMessage } from "@copilotkit/react-native";

<UserMessage content="What's the weather?" />
<AssistantMessage content="It's sunny!" isLoading={false} />

Hooks

The package re-exports react-core's hooks. The two that draw tool calls are worth telling apart.

useFrontendTool

Registers a tool and, optionally, its renderer. The tool is advertised to the model on every run, so it takes a description and (if it should do something on the device) a handler. Render props carry the parsed arguments as args.

// parameters accepts any StandardSchemaV1-compatible schema (Zod, Valibot, ArkType, etc.)
useFrontendTool({
  name: "showChart",
  description: "Display a chart",
  parameters: z.object({ data: z.record(z.unknown()) }),
  render: ({ args }) => <ChartView data={args.data} />,
});

Its render is a React.ComponentType, so the return type is ReactNode and a bare string typechecks — then throws Text strings must be rendered within a <Text> component on a device. FrontendToolRenderFunction<T> is an opt-in type that narrows the return to ReactElement | null; annotate the renderer with it and the compiler rejects the string:

import type { FrontendToolRenderFunction } from "@copilotkit/react-native";

const renderChart: FrontendToolRenderFunction<{
  data: Record<string, unknown>;
}> = ({ args }) => <ChartView data={args.data ?? {}} />;

useFrontendTool({
  name: "showChart",
  description: "Display a chart",
  parameters: z.object({ data: z.record(z.unknown()) }),
  render: renderChart,
});

useRenderTool

Registers a renderer only — nothing is advertised to the model and nothing becomes callable. Use it to draw a tool call somebody else owns, such as a server-side tool. Render props carry the parsed arguments as parameters, and parameters is required on a named renderer. render is already narrowed to ReactElement | null here, so no annotation is needed.

useRenderTool({
  name: "showChart",
  parameters: z.object({ data: z.record(z.unknown()) }),
  render: ({ status, parameters }) => {
    // `parameters` is Partial while the agent is still writing the call.
    if (status === "inProgress") return <Text>Preparing</Text>;
    return <ChartView data={parameters.data} />;
  },
});

name: "*" registers a fallback for every tool call with no renderer of its own, and is the one case that takes no schema:

useRenderTool({
  name: "*",
  render: ({ name, status }) => <Text>{`${name}: ${status}`}</Text>,
});

Deprecated on React Native, for one release. React Native used to export a different hook under this name — one that registered a tool as well as a renderer — so useRenderTool here is currently a compatibility shim over both react-core hooks, scheduled for removal in the next minor. Your existing call still works: name: "*" always registers a renderer only, and any other name carrying description or handler is routed to useFrontendTool the way the old hook did. Either way it warns in development (dev only, once per tool name) and tells you what to rename the call to. One thing does not carry over: on a named renderer the render props are parameters, not args, so a typed render: ({ args }) => … fails with TS2339 (the wildcard's props are untyped, so it still compiles there).

See the useRenderTool reference for the routing rules, the warnings, and the full migration table.

Alternative Import Path

Components can also be imported from the /components subpath:

import { CopilotChat, CopilotModal } from "@copilotkit/react-native/components";

Headless Import Path (custom UI, no chat/attachment native deps)

If you build a fully custom chat UI and only need the provider and the agent/tool hooks, import from @copilotkit/react-native/headless:

import {
  CopilotKitProvider,
  useAgent,
  useFrontendTool,
  useRenderTool,
} from "@copilotkit/react-native/headless";

The default barrel (@copilotkit/react-native) statically re-exports the prebuilt chat components (CopilotChat / CopilotModal / CopilotSidebar / CopilotPopup, which import @gorhom/bottom-sheet) and useAttachments (which imports expo-document-picker + expo-file-system). Even though those are optional peer dependencies, the static re-export forces Metro to resolve them at bundle time — so a headless consumer previously had to install every chat and attachment native dep, or stub them in metro.config.js, to get past Unable to resolve module expo-document-picker.

The /headless entry re-exports only the provider, the platform-agnostic hooks, the render-tool registry, and the core/AG-UI types — none of the chat UI or useAttachments — so those native deps never enter the bundle graph and the metro.config.js stub workaround is no longer needed. Polyfills are still auto-installed, so no separate import "@copilotkit/react-native/polyfills" is required.