JSON to TypeScript for React Components — Props, State, API Responses
- React components benefit from typed props, state, and API responses — interfaces give you all three.
- TanStack Query, SWR, and bare fetch all work cleanly with generated TypeScript interfaces.
- One generated interface can type the API response, the component state, and the props passed down.
Table of Contents
React without TypeScript works, but typed components catch a whole category of bugs before they ship. Props shape mismatches, state updaters that drop fields, API response shapes that drift — all caught at compile time when the types line up. Our JSON to TypeScript generator produces the interfaces that React components use everywhere.
Typing API Responses in a React Component
The simplest case — fetch JSON, type the response:
interface User {
id: number;
email: string;
displayName: string;
}
function UserProfile({ userId }: { userId: number }) {
const [user, setUser] = useState<User | null>(null);
useEffect(() => {
fetch(`/api/users/${userId}`)
.then(r => r.json() as Promise<User>)
.then(setUser);
}, [userId]);
if (!user) return <div>Loading…</div>;
return <div>{user.displayName}</div>;
}
The User interface flows through the fetch response, the state, and the render. Change the shape of the API response and the editor shows every place that breaks.
Generate User by pasting a real response from your API into the tool. Real responses beat hand-written interfaces because they match what actually ships, not what the docs claim.
TanStack Query With Generated Types
TanStack Query (formerly React Query) accepts a generic type parameter that flows through to your component:
interface User {
id: number;
email: string;
displayName: string;
}
function useUser(userId: number) {
return useQuery<User>({
queryKey: ['user', userId],
queryFn: () => fetch(`/api/users/${userId}`).then(r => r.json())
});
}
function UserProfile({ userId }: { userId: number }) {
const { data, isLoading } = useUser(userId);
if (isLoading) return <Loading />;
return <div>{data?.displayName}</div>;
}
data is typed as User | undefined. TypeScript forces you to handle the loading state correctly — you can't access data.displayName without the ? or a guard.
Same pattern works with SWR, urql, Apollo, or bare fetch. The generated interface is framework-agnostic.
Sell Custom Apparel — We Handle Printing & Free ShippingTyping Component Props From JSON
Sometimes you have a JSON mock for a component's props — from a design tool, a content CMS, or a fixtures file. Paste it into the generator and you have a Props interface:
// Mock JSON for a card component:
// {"title": "Hello", "subtitle": "World", "imageUrl": "/x.jpg", "tags": ["a","b"]}
interface CardProps {
title: string;
subtitle: string;
imageUrl: string;
tags: string[];
}
function Card({ title, subtitle, imageUrl, tags }: CardProps) {
// render
}
Rename Root to CardProps (or whatever's idiomatic in your codebase) and drop it into the component file. If your team uses type aliases for props instead of interfaces, swap the keyword — the field list is identical.
useReducer State Shape From JSON
For complex state managed by useReducer, starting from a JSON mock keeps the shape consistent:
// Initial state mock:
// {"step": 1, "answers": {}, "errors": [], "submitting": false}
interface FormState {
step: number;
answers: Record<string, string>;
errors: string[];
submitting: boolean;
}
type FormAction =
| { type: 'next' }
| { type: 'answer'; key: string; value: string }
| { type: 'submit' };
function formReducer(state: FormState, action: FormAction): FormState {
// switch on action.type
}
The generator types answers as an empty object — which becomes any for empty. Tighten to Record<string, string> or a more specific type based on your actual answer shape.
For state that's loaded from an API and then updated locally, use the same User/Item interface for both — type consistency across boundaries.
Prop Drilling vs Typed Context
Generated interfaces work the same for Context as for props:
interface UserContextValue {
user: User | null;
login: (email: string, password: string) => Promise<void>;
logout: () => void;
}
const UserContext = createContext<UserContextValue | null>(null);
The User type is shared between the Context value, the component state inside the provider, and every consumer that reads the context. A change to User shows up everywhere at once.
For Zustand, Jotai, or Redux stores, the same principle applies — generate the slice's shape from a real sample, use it as the store type, and let TypeScript enforce consistency.
Generate Your React Interface
Paste JSON, get TypeScript interfaces for props, state, and API responses.
Open Free JSON to TypeScript GeneratorFrequently Asked Questions
Should I use interface or type for React props?
Either works. interface is slightly more common for props by convention. If your codebase uses type for everything, match that.
How do I type children in a props interface?
children: React.ReactNode — covers JSX elements, strings, numbers, null, arrays, and fragments. Add this field manually after generating from JSON.
Does the generator handle React event handlers?
No — event handlers are not in JSON. Add them to the generated interface manually: onClick: (e: React.MouseEvent) => void.
What about optional props?
The generator marks fields based on what it sees in the sample. Add ? to any field that is truly optional from the caller's perspective.

