This example demonstrates how to use @oax/hooks with React and TanStack Query v5 to create fully type-safe API clients with React hooks. Built on top of the Petstore API, it showcases how oax seamlessly integrates with React applications for an exceptional developer experience.
- ✅ React Query Integration: Generated hooks using TanStack Query v5
- ✅ Type-Safe Hooks: useQuery/useMutation hooks with full TypeScript support
- ✅ Automatic Caching: Built-in caching, background updates, and synchronization
- ✅ Loading States: Proper loading, error, and success state management
- ✅ React 19 Compatible: Works with the latest React version
- ✅ Developer Experience: IntelliSense, autocomplete, and compile-time checks
-
Install Dependencies
pnpm install
-
Start the Development Server
pnpm run dev
-
Open Your Browser Navigate to the localhost URL shown in your terminal (typically http://localhost:5173)
pnpm run dev- Start development serverpnpm run build- Build for productionpnpm run lint- Lint code with Biomepnpm run format- Format code with Biomepnpm run check- Run Biome checks (lint + format)pnpm run typecheck- Type check with TypeScriptpnpm run generate- Regenerate API client from OpenAPI spec
The API client is generated from the Petstore v3 OpenAPI spec:
# This command was used to generate the client
node ../../packages/cli/dist/index.js generate \
-i https://petstore3.swagger.io/api/v3/openapi.json \
-o src/api/client.tsReact hooks are created using the generated client:
import { createHooks } from "@oax/hooks";
import { createClient, operations } from "./client";
const client = createClient("https://petstore3.swagger.io/api/v3/");
export const hooks = createHooks({
apiName: "petstore",
client,
operations,
});import { hooks } from "./api/hooks";
function App() {
const [petId, setPetId] = useState(1);
// Type-safe hook with automatic loading states
const { data: pet, isPending, error } = hooks.getPetById({
params: { petId }
});
return (
<div>
{isPending && <div>Loading...</div>}
{error && <div>Error: {error.message}</div>}
{pet && <div>Pet: {pet.name}</div>}
</div>
);
}Every generated hook provides full type safety:
// ✅ TypeScript knows the exact parameter types
const { data, isPending, error } = hooks.getPetById({
params: { petId: 123 }
});
// ✅ TypeScript knows the exact response type
if (data) {
console.log(data.name); // string
console.log(data.status); // "available" | "pending" | "sold"
console.log(data.category?.name); // string | undefined
}
// ❌ TypeScript prevents invalid usage
hooks.getPetById({ params: { wrongParam: 123 } }); // Compile error
hooks.getPetById({ params: { petId: "invalid" } }); // Compile errorTanStack Query provides powerful features out of the box:
- Caching: Queries are automatically cached and deduplicated
- Background Updates: Data is refetched in the background
- Loading States: Built-in loading, error, and success states
- Optimistic Updates: For mutations with immediate UI feedback
// Query with automatic caching and background refetch
const { data, isPending, error, refetch } = hooks.getPetById({
params: { petId: 1 }
});
// Mutation with loading states and error handling
const mutation = hooks.addPet.useMutation({
onSuccess: () => {
// Invalidate and refetch pet queries
queryClient.invalidateQueries({ queryKey: ["petstore", "getPetById"] });
}
});examples/hooks/
├── src/
│ ├── api/
│ │ ├── client.ts # Generated API client
│ │ └── hooks.ts # React Query hooks
│ ├── App.tsx # Main React component
│ ├── App.css # Styling
│ └── main.tsx # React app entry point
├── package.json # Project dependencies
└── README.md # This file
- @oax/core: Core runtime library for API clients
- @oax/hooks: React Query hooks for oax clients
- @tanstack/react-query: TanStack Query v5 for data fetching
- react: React 19+ for UI components
- zod: Runtime validation schemas
- @vitejs/plugin-react: Vite React plugin
- typescript: TypeScript compiler
- vite: Build tool and dev server
Customize query behavior with TanStack Query options:
const { data, isPending, error } = hooks.getPetById({
params: { petId: 1 },
query: {
refetchInterval: 5000, // Refetch every 5 seconds
staleTime: 1000 * 60, // Consider data stale after 1 minute
enabled: petId > 0, // Only run query when petId is valid
}
});const updatePetMutation = hooks.updatePet.useMutation({
onMutate: async (newPet) => {
// Cancel outgoing refetches
await queryClient.cancelQueries({ queryKey: ["petstore", "getPetById"] });
// Snapshot previous value
const previousPet = queryClient.getQueryData(["petstore", "getPetById", newPet.id]);
// Optimistically update
queryClient.setQueryData(["petstore", "getPetById", newPet.id], newPet);
return { previousPet };
},
onError: (err, newPet, context) => {
// Rollback on error
queryClient.setQueryData(
["petstore", "getPetById", newPet.id],
context?.previousPet
);
},
onSettled: () => {
// Always refetch after error or success
queryClient.invalidateQueries({ queryKey: ["petstore", "getPetById"] });
},
});Handle different types of errors gracefully:
const { data, error, isError } = hooks.getPetById({ params: { petId: 999 } });
if (isError) {
if (error.status === 404) {
return <div>Pet not found</div>;
}
if (error.status >= 500) {
return <div>Server error, please try again later</div>;
}
return <div>Something went wrong: {error.message}</div>;
}When the API changes, regenerate both the client and hooks:
# Update the generated client
pnpm run generate
# TypeScript will immediately show you what changed in your hooks!- Request Deduplication: Multiple components requesting the same data share a single request
- Background Refetching: Data stays fresh without blocking the UI
- Intelligent Caching: Reduces unnecessary network requests
- Partial Data Updates: Only re-render components when their data changes
- Tree Shaking: Only hooks you use are included in the bundle
- Lazy Loading: Query logic is loaded on-demand
- Minimal Runtime: Efficient runtime with minimal overhead
| Feature | Basic Example | Hooks Example |
|---|---|---|
| React Integration | Manual useState/useEffect | Automatic hooks |
| Caching | Manual implementation | Built-in with TanStack Query |
| Loading States | Manual management | Automatic |
| Error Handling | Manual try/catch | Built-in error boundaries |
| Background Updates | Not supported | Automatic |
| Optimistic Updates | Manual implementation | Built-in support |
| DevTools | Limited | Full React Query DevTools |
Try extending this example to:
- Add mutation examples (create, update, delete pets)
- Implement infinite queries for pagination
- Add optimistic updates for better UX
- Integrate with form libraries like React Hook Form
- Add real-time data with WebSocket integration
- Implement offline support with background sync
Happy coding with type-safe React hooks! 🎉