Fetching Data
How to get data into your view and fetch more when the user interacts.
Two Patterns
Section titled “Two Patterns”| Pattern | Hook | When to use |
|---|---|---|
| Initial hydration | useToolInfo | Data passed when view loads |
| User-triggered | useCallTool | User clicks button, needs more data |
Initial Hydration with useToolInfo
Section titled “Initial Hydration with useToolInfo”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> );}Status Flags
Section titled “Status Flags”const { isIdle, isPending, isSuccess, isError, output, error } = useToolInfo();| Flag | Meaning |
|---|---|
isIdle | View hasn’t received data yet |
isPending | Tool is still executing |
isSuccess | Data is available in output |
isError | Tool failed, check error |
User-Triggered Fetching with useCallTool
Section titled “User-Triggered Fetching with useCallTool”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> );}With Callbacks
Section titled “With Callbacks”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); }, } );};With Async/Await
Section titled “With Async/Await”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); }};Type Safety with generateHelpers
Section titled “Type Safety with generateHelpers”Use generateHelpers for autocomplete and type inference. See generateHelpers for setup.
// Import from your typed helpers file, not @usefractal/frac/webimport { useCallTool } from "../frac";
const { callTool, data } = useCallTool("search-hotels");// ^ autocomplete for tool namescallTool({ city: "Paris", checkIn: "2025-12-15" });// ^ type-checked inputsCommon Patterns
Section titled “Common Patterns”Loading State
Section titled “Loading State”function ProductView() { const { isPending, data } = useCallTool("get_product");
if (isPending) { return <Skeleton />; }
if (!data) { return <EmptyState />; }
return <ProductDetails product={data.structuredContent} />;}Refetching
Section titled “Refetching”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> );}Chained Calls
Section titled “Chained Calls”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); };}Anti-patterns
Section titled “Anti-patterns”Don’t fetch on mount
Section titled “Don’t fetch on mount”// Bad: Fetches data on every mountfunction BadView() { const { callTool } = useCallTool("get_data");
useEffect(() => { callTool({}); // Don't do this! }, []);}
// Good: Use initial data from structuredContentfunction GoodView() { const { output } = useToolInfo(); // Data is already here from the tool call}Don’t ignore loading states
Section titled “Don’t ignore loading states”// Bad: Assumes data is always presentfunction BadView() { const { data } = useCallTool("search"); return <div>{data.structuredContent.results.length}</div>; // Crashes!}
// Good: Handle all statesfunction 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>;}