#Repeated API call when useCopilotAction component rendered

1 messages · Page 1 of 1 (latest)

wary moat
#

When LangGrpah agent call Generative UI tool, frontend (nextjs+copilotkit) will pick up and render the component using useCopilotAction.
As the component l involve API calling, when I continue conversation and agent stream response,** I see repeated API calls, like 20 times, as response streaming**.

export const DisplayTransactionHistoryUI = ({ status, handler, args }: any) => {
    ...

    // Only fetch data after the agent has finished streaming (status is complete)
    useEffect(() => {
        // If the agent is still streaming data, don't fetch yet
        if (status === 'inProgress' || status === 'executing') {
            return;
        }

        // Once the streaming is complete, fetch transactions
        const fetchTransactions = async () => {
        // API call
    }, [status, customer_id, date_from, date_to]);

Is this because dev mode? Does the component get mount and re-mount repetitively as response streaming in?
How can I prevent this to happen?
What is the best practice when useCopilotAction component contain API fetch?

wary moat
#

Repeated API call when useCopilotAction component rendered

fleet turtleBOT
#

To help prevent repeated API calls when rendering Generative UI components with CopilotKit agent streaming, you could try using useCoAgentStateRender instead of useEffect. You might want to check out this Travel Planner Demo to see if it helps with your setup.

GitHub

React UI + elegant infrastructure for AI Copilots, AI chatbots, and in-app AI agents. The Agentic last-mile 🪁 - CopilotKit/CopilotKit

wary moat
#

But DisplayTransactionHistoryUI will be displayed as AI assistant response in Chat UI not in app. Also the component be displayed multiple times e.g. user asks to show different transactions 3 times with 3 user messages in same chat conversation, so there are 3 components.

fleet turtleBOT
#

To help debug the repeated API calls, could you please share relevant codes such as:

  1. Full component implementation - especially API call handling and status logic
  2. useCopilotAction usage - how you're invoking it in this or parent components
  3. API call logs - console/network logs showing when calls trigger and how many times
  4. Parent component code - anything controlling when/how DisplayTransactionHistoryUI renders
wary moat
#
  1. I added dependecies of useEffect in original post above. [status, customer_id, date_from, date_to]
  2. CopilotGlobalActions.tsx
export default function CopilotGlobalActions({ children, }: ChatBotProviderProps) {
    // Single hook managing the combined state for Langgraph agent
    const { state: agentState, setState: setAgentState } = useCoAgent<AgentManagedState>({
    ...
    })

    useCopilotAction({
        name: "display_transaction_history",
        description: "Display transaction history of selected customer on web app as react component",
        available: "disabled", // disable it, langgraph backend already handles this
        parameters: [
            ...
        ],
        render: (props) => <DisplayTransactionHistoryUI args={props.args} status={props.status} handler={() => { }} />,
    });

...
export function useChatBotContext(): ChatBotContextType {
    const context = useContext(ChatBotContext);
    if (!context) {
        throw new Error("useChatBotContext must be used within a ChatBotContext.Provider");
    }
    return context;
}

Basically, wrap the app with this context provider.

#

LangGraph agent backend is bind with display_transaction_history tool. When user asks about transactions, this tool is called. Frontend will pick up tool call and DisplayTransactionHistoryUI is rendered as a component (A) in Chat UI while arguments and final response is streaming in.
The first time this component is called, I see 1 API call (when status = 'complete').
But when user continue conversation and agent streaming response, I see lot of API call from the component A.
Note that this is dev mode. In production, I see only 1 API call as expected.

#
  1. layout.tsx
export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode
}>) {
  return (
    <html lang="en">
      <body>
        <CopilotKit
          runtimeUrl="/api/copilotkit"
          agent="basic_agent"
          publicLicenseKey={publicLicenseKey}
        >
          <QueryClientWrapper>
            <AuthProvider>
              <CopilotGlobalActions>
                {children}
              </CopilotGlobalActions>
            </AuthProvider>
          </QueryClientWrapper>
        </CopilotKit>

      </body>
    </html>
  )
}
#
  1. Basically, I see API a request (or twice, I'm not sure) for each new chunk of streaming response.
#

So far, I think not only that the component get re-rendered, it get remounted. Because, I try to use useRef to detect if it isFetched, but it does not work.

#

I just want to know if this is normal behavior? Because production works okay.

fleet turtleBOT
#

No, it is not expected behavior in development for a React component rendered via useCopilotAction to trigger repeated API calls for every streaming response chunk.
Looking at your code, it seems the DisplayTransactionHistoryUI component might be triggering multiple API calls during streaming due to a few potential issues:

The useEffect with dependencies [status, customer_id, date_from, date_to] likely fires whenever any of these values change during streaming. Each chunk of the streamed response might update these values, potentially triggering new API calls before the status reaches 'complete'. The component could also be getting remounted with each streaming chunk, which would reset the internal state.

You might want to track fetches by unique tool call ID to prevent duplicates:

const fetchedForCall = useRef({});

useEffect(() => {
    const callKey = args.toolCallId || 'default-id';
    if (status !== 'complete' || fetchedForCall.current[callKey]) return;
    fetchedForCall.current[callKey] = true;
    // API call logic here
}, [status, args.toolCallId]);

Alternatively, you could simplify the condition to only depend on status:

const fetched = useRef(false);

useEffect(() => {
    if (status !== 'complete' || fetched.current) return;
    fetched.current = true;
    // API call here
}, [status]);

You might also want to ensure your component has a stable key to prevent unnecessary remounting:

<DisplayTransactionHistoryUI key={args.toolCallId} ... />

Hope one of these approaches helps resolve the multiple API calls issue!

wary moat
#

I actually tried all your suggestions before I create this thread but it did not work.
The only way that work is use React Query in dev mode which I should do use React Query anyway.
Or leave it as is as production works okay (fetch only once).