3.2: Data Modeling with Types
Learning Objectives
- Parse API payloads with
JSON.parseand inspect the resulting shape - Model remote response data using TypeScript types for safer UI rendering
- Render loading feedback with
ActivityIndicatorwhile 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:
- Convert response text into a JavaScript value.
- Validate and model the shape of that value.
- 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.
Parsing JSON in Practice
Section titled “Parsing JSON in Practice”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.
Typed Response Modeling
Section titled “Typed Response Modeling”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 and State Pattern
Section titled “ActivityIndicator and State Pattern”ActivityIndicator gives immediate feedback that work is happening.1
Use a three-state flow:
isLoading = truewhile request/parsing is happeningerrorif request or parsing failsdatawhen parsing succeeds
New TypeScript Operators
Section titled “New TypeScript Operators”The demo uses three TypeScript tools that look similar at first, but do different jobs:
isfor type guardsinfor property checksasfor type assertions
is (Type Guard Return Type)
Section titled “is (Type Guard Return Type)”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.
in (Does This Property Exist?)
Section titled “in (Does This Property Exist?)”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.
as (Type Assertion)
Section titled “as (Type Assertion)”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 valueJSON.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.
- Define response types (
Movie,MoviesResponse). - Fetch payload with
fetchand checkresponse.ok. - Parse JSON and map to typed state.
- Display
ActivityIndicatorwhile loading. - Render success list or an error message.
Exercise
Section titled “Exercise”In this exercise, the starter app fetches from DummyJSON todos but leaves key modeling and rendering pieces for you to complete.
- Click the external link icon (top-left of each Snack) to open in a new tab.
- Click the blue “Save” button to save to your account.
- 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.
Summary
Section titled “Summary”- JSON parsing converts API text into data your app can use.
- Type models make UI code safer and easier to maintain.
ActivityIndicatorimproves perceived responsiveness during async work.- The loading-error-success pattern keeps rendering logic easy to reason about.