Skip to content

3.2: Data Modeling with Types

Learning Objectives

  • Parse API payloads with JSON.parse and inspect the resulting shape
  • Model remote response data using TypeScript types for safer UI rendering
  • Render loading feedback with ActivityIndicator while requests are in flight
  • Apply a loading-error-success state pattern to keep async UI predictable

In 3.1, we focused on when to fetch using useEffect.

Now we focus on what happens after the response arrives:

  1. Convert response text into a JavaScript value.
  2. Validate and model the shape of that value.
  3. Render the UI with loading, error, and success states.

The API returns JSON text, not typed TypeScript objects. Parsing and modeling are the bridge from network bytes to trustworthy UI.

You will commonly see response.json() used directly. That is perfectly fine for most cases. In this chapter, we also show the explicit pattern with response.text() + JSON.parse() so one can see exactly where parsing happens.

const response = await fetch('https://reactnative.dev/movies.json');
const text = await response.text();
const parsed: unknown = JSON.parse(text);

Why parse into unknown first? Because it forces you to prove the shape before trusting it.

To know what data types to use in our TS types, we must see the output of the fetch request. You can do this by navigating to https://reactnative.dev/movies.json directly and seeing the output.

{
"title": "The Basics - Networking",
"description": "Your app fetched this from a remote endpoint!",
"movies": [
{ "id": "1", "title": "Star Wars", "releaseYear": "1977" },
{ "id": "2", "title": "Back to the Future", "releaseYear": "1985" },
{ "id": "3", "title": "The Matrix", "releaseYear": "1999" },
{ "id": "4", "title": "Inception", "releaseYear": "2010" },
{ "id": "5", "title": "Interstellar", "releaseYear": "2014" }
]
}

From this output, we can determine the following types:

type Movie = {
id: string;
title: string;
releaseYear: string;
};
type MoviesResponse = {
movies: Movie[];
};

Then parse and map data into that model before rendering.

const parsed = JSON.parse(text) as MoviesResponse;
setMovies(parsed.movies);

ActivityIndicator gives immediate feedback that work is happening.1

Use a three-state flow:

  • isLoading = true while request/parsing is happening
  • error if request or parsing fails
  • data when parsing succeeds
Diagram

The demo uses three TypeScript tools that look similar at first, but do different jobs:

  • is for type guards
  • in for property checks
  • as for type assertions
function isMoviesResponse(value: unknown): value is MoviesResponse {
// checks...
}

value is MoviesResponse tells TypeScript: if this function returns true, then value can be treated as MoviesResponse.

This is how we safely narrow unknown data before rendering.

if (!('movies' in value)) return false;

in checks whether a property key exists on an object. It is useful before reading a field from unknown input.

In the snack, we use it to verify that the parsed JSON includes a movies key before trying to process it.

const maybeMovies = (value as { movies: unknown }).movies;

as tells TypeScript to treat a value as a specific type at compile time. It does not change runtime data.

In this lesson, as is used only after basic runtime checks (typeof, in, Array.isArray) so the assertion is backed by evidence instead of blind trust.

Deeper Dive: When should I use JSON.stringify?

JSON.parse and JSON.stringify are opposite directions:

  • JSON.parse(text) converts JSON text into a JS value
  • JSON.stringify(value) converts a JS value into JSON text

In API-fetch lessons, JSON.parse is the main step.

JSON.stringify is still useful for:

  • quick debug previews
  • preparing values for storage in later persistence lessons

In the demo above, the debug panel uses JSON.stringify so students can inspect the exact array shape while comparing it with typed UI rendering.

  1. Define response types (Movie, MoviesResponse).
  2. Fetch payload with fetch and check response.ok.
  3. Parse JSON and map to typed state.
  4. Display ActivityIndicator while loading.
  5. Render success list or an error message.

In this exercise, the starter app fetches from DummyJSON todos but leaves key modeling and rendering pieces for you to complete.

  1. Click the external link icon (top-left of each Snack) to open in a new tab.
  2. Click the blue “Save” button to save to your account.
  3. Copy the Snack URL once completed and submit on Moodle.

Hint

Start by checking the DummyJSON todos docs and writing the two missing types before touching render code.

In loadTodos, focus on a strict sequence: fetch raw text, parse once to unknown, validate shape with a type guard, then push only the todos array into state.

The list section is intentionally under-typed in the starter. After your types are in place, remove temporary casts and make state, keyExtractor, and renderItem type-safe.

Use the UI output to verify shape correctness: each row should show todo text, user ID, and completed/pending status.

  • JSON parsing converts API text into data your app can use.
  • Type models make UI code safer and easier to maintain.
  • ActivityIndicator improves perceived responsiveness during async work.
  • The loading-error-success pattern keeps rendering logic easy to reason about.
  1. https://reactnative.dev/docs/activityindicator