React setup
1. Install
pnpm add schelopnpm add schelonpm install schelonpm install scheloZod is a peer dependency (you define schemas with it). If it is not already in the project:
pnpm add schelo zodpnpm add schelo zodnpm install schelo zodnpm install schelo zod2. Schema module
From your app folder, run the CLI and choose React:
npx schelo initnpx schelo initThat creates lib/api-schemas.ts or src/lib/api-schemas.ts and exports interceptor from createInterceptor({ ... }). You can add the file by hand instead if you prefer.
Example config:
import { createInterceptor } from "schelo";
import { z } from "zod";
const healthSchema = z.object({ status: z.literal("ok") });
const apiErrorSchema = z.object({ error: z.string() });
export const interceptor = createInterceptor({
mode: "warn",
warnOnUnmatched: true,
routes: {
"GET /api/health": {
response: z.union([healthSchema, apiErrorSchema]),
},
"POST /api/items": {
validate: false,
request: z.object({ title: z.string() }),
response: z.union([z.object({ id: z.string(), title: z.string() }), apiErrorSchema]),
},
},
});import { createInterceptor } from "schelo";
import { z } from "zod";
const healthSchema = z.object({ status: z.literal("ok") });
const apiErrorSchema = z.object({ error: z.string() });
export const interceptor = createInterceptor({
mode: "warn",
warnOnUnmatched: true,
routes: {
"GET /api/health": {
response: z.union([healthSchema, apiErrorSchema]),
},
"POST /api/items": {
validate: false,
request: z.object({ title: z.string() }),
response: z.union([z.object({ id: z.string(), title: z.string() }), apiErrorSchema]),
},
},
});3. Enable in the browser entry
Call interceptor.enable() in your browser entry file, before createRoot(...).render(...) (or ReactDOM.render). Import interceptor with a path that resolves from that entry (for example ./lib/api-schemas or ../lib/api-schemas).
Typical entry files:
- React (Vite):
src/main.tsx,main.jsx, orindex.tsx - CRA:
src/index.tsxorindex.js
Do not rely on useEffect for the first enable(): a child component may fetch before that effect runs. Put wrappers (StrictMode, router, and so on) inside render; keep enable() above it.
4. Example
import { createRoot } from "react-dom/client";
import { interceptor } from "./lib/api-schemas"; // import from api-schemas.ts
import App from "./App";
import "./index.css";
interceptor.enable(); //enable the interceptor
createRoot(document.getElementById("root")!).render(<App />);import { createRoot } from "react-dom/client";
import { interceptor } from "./lib/api-schemas"; // import from api-schemas.ts
import App from "./App";
import "./index.css";
interceptor.enable(); //enable the interceptor
createRoot(document.getElementById("root")!).render(<App />);5. disable() (optional)
Rarely needed: use interceptor.disable() in tests or when you need to restore the real fetch.
interceptor.disable();interceptor.disable();