Skip to content

Commit cf6780b

Browse files
committed
fix: use refs for agent dispatch in QR pick polling
Prevents stale closure issues when the setTimeout fires after modal close. Also stops polling immediately after first pick to avoid duplicate agent runs.
1 parent 324c82a commit cf6780b

1 file changed

Lines changed: 21 additions & 6 deletions

File tree

apps/app/src/app/page.tsx

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"use client";
22

3-
import { useEffect, useState } from "react";
3+
import { useCallback, useEffect, useRef, useState } from "react";
44
import { ExampleLayout } from "@/components/example-layout";
55
import { useGenerativeUIExamples, useExampleSuggestions } from "@/hooks";
66
import { ExplainerCardsPortal } from "@/components/explainer-cards";
@@ -21,10 +21,22 @@ export default function HomePage() {
2121
const { agent } = useAgent();
2222
const { copilotkit } = useCopilotKit();
2323

24+
// Ref to always have the latest agent/copilotkit for async callbacks
25+
const agentRef = useRef(agent);
26+
const copilotkitRef = useRef(copilotkit);
27+
agentRef.current = agent;
28+
copilotkitRef.current = copilotkit;
29+
30+
const sendPrompt = useCallback((prompt: string) => {
31+
const a = agentRef.current;
32+
const ck = copilotkitRef.current;
33+
a.addMessage({ id: crypto.randomUUID(), content: prompt, role: "user" });
34+
ck.runAgent({ agent: a });
35+
}, []);
36+
2437
const handleTryDemo = (demo: DemoItem) => {
2538
setDemoDrawerOpen(false);
26-
agent.addMessage({ id: crypto.randomUUID(), content: demo.prompt, role: "user" });
27-
copilotkit.runAgent({ agent });
39+
sendPrompt(demo.prompt);
2840
};
2941

3042
// Reset scan status when QR modal opens
@@ -35,26 +47,29 @@ export default function HomePage() {
3547
// Poll for QR pick status
3648
useEffect(() => {
3749
if (!qrOpen) return;
50+
let picked = false;
3851
const interval = setInterval(async () => {
52+
if (picked) return;
3953
try {
4054
const res = await fetch(`/api/pick?sessionId=${qrSessionId}`);
4155
const data = await res.json();
4256
if (data.status === "scanned") {
4357
setScanStatus("scanned");
4458
} else if (data.status === "picked" && data.prompt) {
59+
picked = true;
4560
setScanStatus("picked");
61+
clearInterval(interval);
4662
setTimeout(() => {
4763
setQrOpen(false);
48-
agent.addMessage({ id: crypto.randomUUID(), content: data.prompt, role: "user" });
49-
copilotkit.runAgent({ agent });
64+
sendPrompt(data.prompt);
5065
}, 800);
5166
}
5267
} catch {
5368
// ignore polling errors
5469
}
5570
}, 2000);
5671
return () => clearInterval(interval);
57-
}, [qrOpen, qrSessionId, agent, copilotkit]);
72+
}, [qrOpen, qrSessionId, sendPrompt]);
5873

5974
// Widget bridge: handle messages from widget iframes
6075
useEffect(() => {

0 commit comments

Comments
 (0)