Blog
Wild & Free Tools

JSON to TypeScript for React Components — Props, State, API Responses

Last updated: March 2026 7 min read
Quick Answer

Table of Contents

  1. Typing API Responses
  2. TanStack Query with Generated Types
  3. Component Props From JSON
  4. useReducer State From JSON
  5. Prop Drilling vs Typed Context
  6. Frequently Asked Questions

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 Shipping

Typing 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 Generator

Frequently 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.

David Rosenberg
David Rosenberg Technical Writer

David spent ten years as a software developer before shifting to technical writing covering developer productivity tools.

More articles by David →
Launch Your Own Clothing Brand — No Inventory, No Risk