Skip to content

Commit d5f11c1

Browse files
mxmzbclaude
andcommitted
fix(saas-demo): render showTransactions via useComponent with stable deps
Display-only generative UI must use useComponent (not useFrontendTool): its render is unconditional so the card persists after the tool call completes. Also pass [transactions, cards] as deps so the renderer re-registers when the data loads -- otherwise the closure captures the initial empty transactions and the list renders empty. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 6d455e7 commit d5f11c1

1 file changed

Lines changed: 52 additions & 53 deletions

File tree

  • examples/showcases/banking/src/app

examples/showcases/banking/src/app/page.tsx

Lines changed: 52 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { useEffect, useReducer, useState } from "react";
44
import {
55
useAgentContext,
66
useHumanInTheLoop,
7-
useFrontendTool,
7+
useComponent,
88
} from "@copilotkit/react-core/v2";
99
import { z } from "zod";
1010
import type { NewCardRequest, Transaction } from "@/app/api/v1/data";
@@ -319,60 +319,59 @@ export default function Page() {
319319
},
320320
});
321321

322-
// Showcase usage of generative UI
323-
useFrontendTool({
324-
name: "showTransactions",
325-
description:
326-
"Displays a list of transactions upon request. At least one parameter is required per request",
327-
available: PERMISSIONS.SHOW_TRANSACTIONS.includes(currentUser.role),
328-
parameters: z.object({
329-
card4Digits: z
330-
.string()
331-
.describe("the last 4 digits of the card")
332-
.optional(),
333-
policyId: z
334-
.string()
335-
.describe("the id of the policy (figured out by copilot)")
336-
.optional(),
337-
transactionTitle: z
338-
.string()
339-
.describe("the title of the transaction")
340-
.optional(),
341-
}),
342-
handler: async () => {
343-
await new Promise((resolve) => setTimeout(resolve, 3000));
344-
},
345-
render: ({ status, args }) => {
346-
const { card4Digits, policyId, transactionTitle } = args;
347-
348-
let filteredTransactions = transactions;
349-
if (card4Digits) {
350-
filteredTransactions = filterTransactionsByCardLast4(
351-
transactions,
352-
cards,
353-
card4Digits,
354-
);
355-
} else if (policyId) {
356-
filteredTransactions = filterTransactionsByPolicyId(
357-
transactions,
358-
policyId,
359-
);
360-
} else if (transactionTitle) {
361-
filteredTransactions = filterTransactionByTitle(
362-
transactions,
363-
transactionTitle,
364-
);
365-
}
366-
367-
if (status === "inProgress") {
368-
return "Loading...";
369-
} else if (!filteredTransactions) {
370-
return "Problem fetching transactions";
371-
} else {
322+
// Showcase usage of generative UI. Display-only components use `useComponent`
323+
// (not `useFrontendTool`): its render is unconditional, so the rendered card
324+
// persists in the transcript after the tool call completes. A handler-driven
325+
// `useFrontendTool` render only shows transiently while executing.
326+
useComponent(
327+
{
328+
name: "showTransactions",
329+
description:
330+
"Displays a list of transactions upon request. At least one parameter is required per request",
331+
parameters: z.object({
332+
card4Digits: z
333+
.string()
334+
.describe("the last 4 digits of the card")
335+
.optional(),
336+
policyId: z
337+
.string()
338+
.describe("the id of the policy (figured out by copilot)")
339+
.optional(),
340+
transactionTitle: z
341+
.string()
342+
.describe("the title of the transaction")
343+
.optional(),
344+
}),
345+
render: ({ card4Digits, policyId, transactionTitle }) => {
346+
let filteredTransactions = transactions;
347+
if (card4Digits) {
348+
filteredTransactions = filterTransactionsByCardLast4(
349+
transactions,
350+
cards,
351+
card4Digits,
352+
);
353+
} else if (policyId) {
354+
filteredTransactions = filterTransactionsByPolicyId(
355+
transactions,
356+
policyId,
357+
);
358+
} else if (transactionTitle) {
359+
filteredTransactions = filterTransactionByTitle(
360+
transactions,
361+
transactionTitle,
362+
);
363+
}
364+
365+
if (!filteredTransactions) {
366+
return <>Problem fetching transactions</>;
367+
}
372368
return <TransactionsList transactions={filteredTransactions} compact />;
373-
}
369+
},
374370
},
375-
});
371+
// Re-register the renderer when the underlying data loads/changes; otherwise
372+
// the closure captures the initial empty `transactions`/`cards`.
373+
[transactions, cards],
374+
);
376375

377376
// Enable pin changing with co pilot
378377
useHumanInTheLoop({

0 commit comments

Comments
 (0)