);
}
```
**Why `state`:** Tasks and progress survive widget close/reopen. The LLM can read the state to answer "how many tasks are left?"
### Convenience Hook: `useWidgetState`
For simpler access:
```tsx
import { useWidgetState } from "mcp-use/react";
function Counter() {
const [state, setState] = useWidgetState({ count: 0 });
return (
);
}
```
## Ephemeral State: React `useState`
Use React `useState` for UI-only state that doesn't need to persist or be visible to the LLM.
```tsx
import { useState } from "react";
function ProductCard({ product }) {
// Ephemeral: hover state resets on reopen, LLM doesn't need it
const [isHovered, setIsHovered] = useState(false);
return (
);
}
```
**Why `useState`:** Hover state is purely visual, resets on reopen, and the LLM never needs to know about it.
## Common Mistake
```tsx
// DON'T: useState loses data on close, LLM can't see it
const [selected, setSelected] = useState(null);
// DO: state persists, LLM sees selections
const { state, setState } = useWidget();
const selected = state?.selected;
```
## Calling Other Tools: `callTool`
Widgets can call any tool registered on the server:
```tsx
import { useWidget } from "mcp-use/react";
import { useState } from "react";
function SearchWidget() {
const { callTool } = useWidget();
const [result, setResult] = useState(null);
const [loading, setLoading] = useState(false);
const handleSearch = async (query: string) => {
setLoading(true);
try {
const response = await callTool("search-items", { query });
setResult(response.content);
} catch (err) {
console.error("Search failed:", err);
} finally {
setLoading(false);
}
};
return (
{result &&
{JSON.stringify(result, null, 2)}
}
);
}
```
## Triggering LLM Response: `sendFollowUpMessage`
Trigger the LLM to respond from a widget interaction:
```tsx
import { useWidget } from "mcp-use/react";
function AnalyzeButton() {
const { sendFollowUpMessage } = useWidget();
return (
);
}
```
Use this when:
- User clicks "Find best option" and the LLM should reason about the data
- User completes a selection and the LLM should provide recommendations
- Widget needs the LLM to take action based on current state
## Opening External URLs: `openExternal`
Redirect users to external sites:
```tsx
import { useWidget } from "mcp-use/react";
function CheckoutButton({ checkoutUrl }) {
const { openExternal } = useWidget();
return (
);
}
```
## Combined Example
Todo list with persistent tasks, ephemeral viewing state, and LLM interaction:
```tsx
import { McpUseProvider, useWidget } from "mcp-use/react";
import { useState } from "react";
export default function TodoWidget() {
const { props, isPending, state, setState, sendFollowUpMessage } =
useWidget();
// PERSISTENT: Tasks survive widget close/reopen, LLM sees them
const tasks = state?.tasks || props.tasks || [];
// EPHEMERAL: Which task is being viewed (resets on reopen)
const [viewing, setViewing] = useState(null);
if (isPending)
return (