Skip to content

Integration Recipes

A handful of common screens, with the code and a sentence of why you’d use each. Take them, paste them, restyle them.

When: on app load, you want to show a sign‑in screen until the user has authenticated.

import { useThinkworkAuth } from "@thinkwork/react-native-sdk";
function App() {
const { status } = useThinkworkAuth();
if (status === "unknown") return <Splash />;
if (status !== "signed-in") return <SignInScreen />;
return <AppShell />;
}
function SignInScreen() {
const { signInWithGoogle } = useThinkworkAuth();
return <Button onPress={signInWithGoogle} title="Continue with Google" />;
}

When: the user has several agents and you want their last choice to stick across app launches.

The SDK gives you the agent list but intentionally stays out of persistence — different apps want different storage (AsyncStorage, MMKV, whatever). This is the typical shape:

import AsyncStorage from "@react-native-async-storage/async-storage";
import type { Agent } from "@thinkwork/react-native-sdk";
const KEY = "myapp.selectedAgentId";
export function useSelectedAgent(agents: Agent[]) {
const [selectedId, setSelectedId] = React.useState<string | null>(null);
React.useEffect(() => {
AsyncStorage.getItem(KEY).then(setSelectedId);
}, []);
const selected = agents.find(a => a.id === selectedId) ?? agents[0] ?? null;
const setId = (id: string | null) => {
setSelectedId(id);
id ? AsyncStorage.setItem(KEY, id) : AsyncStorage.removeItem(KEY);
};
return { selectedAgent: selected, setSelectedAgentId: setId };
}

Use it:

const { user } = useThinkworkAuth();
const { agents } = useAgents({ tenantId: user?.tenantId });
const { selectedAgent, setSelectedAgentId } = useSelectedAgent(agents);

When: you’re showing the user their threads.

const { threads, loading, refetch } = useThreads({ tenantId, agentId: selectedAgent?.id });
return (
<FlatList
data={threads}
keyExtractor={(t) => t.id}
renderItem={({ item }) => <ThreadRow thread={item} />}
refreshing={loading}
onRefresh={refetch}
/>
);

When: the user has tapped into a thread and you’re showing messages and a composer.

A minimal version:

function ChatScreen({ threadId }: { threadId: string }) {
const { thread } = useThread(threadId);
const { messages } = useMessages(threadId);
const sendMessage = useSendMessage();
const [draft, setDraft] = React.useState("");
const submit = async () => {
const text = draft.trim();
if (!text) return;
setDraft("");
await sendMessage(threadId, text);
};
return (
<>
<Header title={thread?.title ?? "Chat"} />
<FlatList
data={[...messages].reverse()}
keyExtractor={(m) => m.id}
renderItem={({ item }) => <Bubble role={item.role} text={item.content} />}
/>
<Composer value={draft} onChange={setDraft} onSend={submit} />
</>
);
}

A full‑featured version with markdown rendering and auto‑scroll lives in examples/chat-ui/ThinkworkChat.tsx — copy it, restyle it.

When: your user types a message in a “compose” box and you want both the thread and the first message created in one call.

const createThread = useCreateThread();
async function start(text: string) {
const thread = await createThread({
tenantId,
agentId: selectedAgent.id,
title: text.slice(0, 60),
channel: "CHAT",
firstMessage: text,
});
router.push(`/thread/${thread.id}`);
}

One round trip. The agent starts responding as soon as the thread is created.

When: you have a “Chat” or “Brain” tab and want the standard red unread count dot.

function ChatTabBadge() {
const { user } = useThinkworkAuth();
const { agents } = useAgents({ tenantId: user?.tenantId });
const { selectedAgent } = useSelectedAgent(agents);
const { count } = useUnreadThreadCount({
tenantId: user?.tenantId,
agentId: selectedAgent?.id,
});
return count > 0 ? <Badge>{count}</Badge> : null;
}

When the user reads a thread, the count goes down automatically — no manual plumbing needed.

Optimistic “just read” (optional polish)

Section titled “Optimistic “just read” (optional polish)”

When: you want the unread dot to disappear the instant a user taps a thread, before the server round trip returns.

Keep a local set alongside the server state:

const locallyRead = useRef(new Set<string>());
function openThread(t: Thread) {
locallyRead.current.add(t.id);
updateThread(t.id, { lastReadAt: new Date().toISOString() });
router.push(`/thread/${t.id}`);
}
function isUnread(t: Thread) {
if (locallyRead.current.has(t.id)) return false;
return /* normal unread logic */;
}

The server truth still catches up; the local set just shortcuts the UX.

What to do when ThinkWork isn’t configured

Section titled “What to do when ThinkWork isn’t configured”

When: your app ships to environments where ThinkWork isn’t set up (dev builds, pre‑activation, etc.) and you want to hide the features gracefully.

const isThinkworkConfigured = Boolean(
process.env.EXPO_PUBLIC_THINKWORK_GRAPHQL_URL &&
process.env.EXPO_PUBLIC_COGNITO_USER_POOL_ID,
);
return isThinkworkConfigured ? (
<ThinkworkProvider config={config}><App /></ThinkworkProvider>
) : <App />;

Hide tab‑bar entries that depend on ThinkWork behind the same check so users never see a broken tab.