Skip to content

Fetching Data

How to get data into your view and fetch more when the user interacts.

PatternHookWhen to use
Initial hydrationuseToolInfoData passed when view loads
User-triggereduseCallToolUser clicks button, needs more data

When the host calls your view’s tool, the server returns structuredContent. Access it with useToolInfo:

import { useToolInfo } from "@usefractal/frac/web";
type FlightData = {
flights: Array<{ id: string; name: string; price: number }>;
};
export function FlightView() {
const { isSuccess, output } = useToolInfo<FlightData>();
if (!isSuccess) {
return <div>Loading...</div>;
}
const { flights } = output.structuredContent;
return (
<ul>
{flights.map(flight => (
<li key={flight.id}>{flight.name} - ${flight.price}</li>
))}
</ul>
);
}
const { isIdle, isPending, isSuccess, isError, output, error } = useToolInfo();
FlagMeaning
isIdleView hasn’t received data yet
isPendingTool is still executing
isSuccessData is available in output
isErrorTool failed, check error

When the user clicks something and you need more data:

import { useCallTool } from "@usefractal/frac/web";
export function FlightView() {
const { callTool, isPending, data, isError, error } = useCallTool("get_flight_details");
const handleViewDetails = (flightId: string) => {
callTool({ flightId });
};
return (
<div>
<button onClick={() => handleViewDetails("AF123")} disabled={isPending}>
{isPending ? "Loading..." : "View Details"}
</button>
{isError && <p>Error: {String(error)}</p>}
{data && <pre>{JSON.stringify(data.structuredContent, null, 2)}</pre>}
</div>
);
}
const { callTool } = useCallTool("book_hotel");
const handleBooking = () => {
callTool(
{ hotelId: "123" },
{
onSuccess: (response) => {
console.log("Booking confirmed:", response.structuredContent);
setBookingComplete(true);
},
onError: (error) => {
console.error("Booking failed:", error);
setShowErrorModal(true);
},
onSettled: () => {
// Runs after success or error
setSubmitting(false);
},
}
);
};
const { callToolAsync, isPending } = useCallTool("check_availability");
const handleCheck = async (date: string) => {
try {
const result = await callToolAsync({ date });
if (result.structuredContent.available) {
setStep("payment");
} else {
setShowUnavailableMessage(true);
}
} catch (error) {
setError(error);
}
};

Use generateHelpers for autocomplete and type inference. See generateHelpers for setup.

// Import from your typed helpers file, not @usefractal/frac/web
import { useCallTool } from "../frac";
const { callTool, data } = useCallTool("search-hotels");
// ^ autocomplete for tool names
callTool({ city: "Paris", checkIn: "2025-12-15" });
// ^ type-checked inputs
function ProductView() {
const { isPending, data } = useCallTool("get_product");
if (isPending) {
return <Skeleton />;
}
if (!data) {
return <EmptyState />;
}
return <ProductDetails product={data.structuredContent} />;
}
function StockView() {
const { callTool, data, isPending } = useCallTool("get_stock_price");
const [lastFetch, setLastFetch] = useState<Date | null>(null);
const refresh = () => {
callTool({ symbol: "AAPL" }, {
onSuccess: () => setLastFetch(new Date()),
});
};
return (
<div>
<button onClick={refresh} disabled={isPending}>
{isPending ? "Refreshing..." : "Refresh"}
</button>
{lastFetch && <p>Last updated: {lastFetch.toLocaleTimeString()}</p>}
{data && <StockPrice price={data.structuredContent.price} />}
</div>
);
}
function BookingView() {
const { callToolAsync: checkAvailability } = useCallTool("check_availability");
const { callToolAsync: createBooking } = useCallTool("create_booking");
const handleBook = async (roomId: string, dates: DateRange) => {
const availability = await checkAvailability({ roomId, ...dates });
if (!availability.structuredContent.available) {
setError("Room not available for these dates");
return;
}
const booking = await createBooking({
roomId,
...dates,
priceId: availability.structuredContent.priceId,
});
setConfirmation(booking.structuredContent);
};
}
// Bad: Fetches data on every mount
function BadView() {
const { callTool } = useCallTool("get_data");
useEffect(() => {
callTool({}); // Don't do this!
}, []);
}
// Good: Use initial data from structuredContent
function GoodView() {
const { output } = useToolInfo();
// Data is already here from the tool call
}
// Bad: Assumes data is always present
function BadView() {
const { data } = useCallTool("search");
return <div>{data.structuredContent.results.length}</div>; // Crashes!
}
// Good: Handle all states
function GoodView() {
const { data, isPending, isError } = useCallTool("search");
if (isPending) return <Loading />;
if (isError) return <Error />;
if (!data) return null;
return <div>{data.structuredContent.results.length}</div>;
}