Course outline

React Native / Asynchronous and data ownership / Lesson 9

Decoded models and repository boundaries

Validate runtime shapes before rendering and separate transport records, accepted domain values, and public presentation.

About 4 min readContent reviewed
On this page

Lesson goals

  • A type annotation does not validate remote bytes
  • Decode into a narrow accepted value
  • Project presentation explicitly from accepted data

A type annotation does not validate remote bytes

React Native networking can produce a response successfully while the HTTP operation failed or its JSON shape changed. Evaluate the response contract before decoding the success body, and validate runtime values before constructing accepted models. A TypeScript assertion cannot turn a string seat count into a number or prove an identifier exists. Distinguish transport failure, unsuccessful status, malformed syntax, and invalid domain fields. Returning an empty list for every failure hides these differences. Keep request and session freshness checks after decoding too: a perfectly shaped record can still belong to an obsolete owner.

Decode into a narrow accepted value

The first program validates an object, a nonblank identifier, and nonnegative safe integer seats. It returns a fresh object containing only fields the model accepts. The identifier policy is intentionally synthetic; real identifiers need their documented syntax and normalization rules. An explicit zero seat count is accepted, while a numeric string is rejected instead of silently coerced. Extra transport fields are ignored by this chosen compatibility policy, not treated as authorized model fields. A stricter service contract may reject unknown fields. State that choice rather than letting incidental object spreading decide it.

javascript Standalone JavaScript model with synthetic local inputs.
function decodeWorkshop(raw) {
  if (!raw || typeof raw !== 'object' || Array.isArray(raw)) throw new Error('INVALID_RECORD');
  if (typeof raw.id !== 'string' || raw.id.trim() === '') throw new Error('INVALID_ID');
  if (!Number.isSafeInteger(raw.seats) || raw.seats < 0) throw new Error('INVALID_SEATS');
  return {id: raw.id, seats: raw.seats};
}
console.log(JSON.stringify(decodeWorkshop({id: 'w-7', seats: 3, extra: 'ignored'})));
try { decodeWorkshop({id: 'w-8', seats: '3'}); }
catch (error) { console.log(error.message); }
Expected output
{"id":"w-7","seats":3}
INVALID_SEATS

The accepted result has numeric seats and a fresh narrow shape. No JSON endpoint or native networking operation is exercised.

Project presentation explicitly from accepted data

The second program creates a card description without copying an internal note. A projection makes the visible contract reviewable and prevents an unrelated added field from automatically appearing in a public result. It is not server authorization and does not make sensitive data safe merely because one card omits it. Prefer not to distribute unnecessary private fields to the client in the first place. Keep formatting policy separate from data identity, and retain units in model names. A localized card should map stable values into message resources rather than concatenate translated fragments around arbitrary numbers.

javascript Standalone JavaScript model with synthetic local inputs.
function cardFromAccepted(record) {
  return {id: record.id, label: 'Seats: ' + record.seats};
}
const accepted = {id: 'w-7', seats: 3, internalNote: 'synthetic only'};
const card = cardFromAccepted(accepted);
console.log(JSON.stringify(card));
console.log(Object.hasOwn(card, 'internalNote'));
console.log(accepted.internalNote);
Expected output
{"id":"w-7","label":"Seats: 3"}
false
synthetic only

The projection excludes the note and leaves its source unchanged. It demonstrates a value boundary, not a privacy certification for the application.

A repository owns source policy rather than a screen

A repository can coordinate transport, cache, decoding, and data consistency behind an explicit interface. Inject it into a model so tests can substitute deterministic data. Do not store a transient navigation object or UI context inside the repository to show errors from an obsolete screen. Return meaningful outcomes and let the current owner decide presentation. When one record is malformed, choose whether the whole response fails or partial data is allowed with a visible qualification. Duplicate identifiers require collection-level validation even when each individual record passes its decoder. The correct policy depends on the task’s consequence.

Verify shape, meaning, and presentation independently

Use small fixtures for null, arrays, missing fields, wrong types, negative quantities, unsafe integers, valid zero, and unknown fields. Assert stable failure categories rather than a random property-access exception. Test collection uniqueness and the partial-response policy separately. Then render already accepted models in a configured component host so visual defects are not entangled with transport decoding. Pure Node tests establish decoder and projection behavior only. They do not verify fetch transport, native TLS policy, framework integration, or actual Text layout; no remote service is contacted by the models.

Common errors and fixes

A string quantity reaches arithmetic as a number

Why it happens
A static assertion replaced runtime validation.
How to fix it
Validate and normalize at one documented decoding boundary.
Prevent it next time
Test numeric strings separately from numeric values.

An added transport field leaks into a public card

Why it happens
The presentation copied the complete source object.
How to fix it
Project explicit allowed fields and minimize upstream data.
Prevent it next time
Add a synthetic internal field to projection fixtures.

Two individually valid records produce duplicate keys

Why it happens
Uniqueness was not checked at the collection boundary.
How to fix it
Validate identifiers across the accepted collection before rendering.
Prevent it next time
Include conflicting identifiers in response tests.

Check your understanding

1. Should missing seats be silently normalized to zero?

Show worked solution

Only if the service and domain contract explicitly defines absence as zero. Otherwise it erases the distinction between unknown availability and a known empty capacity. Reject the missing field or model absence explicitly, and keep display fallback decisions separate.

2. Does excluding internalNote from a card prove the client never received private data?

Show worked solution

No. The source already contains it in this synthetic example. Projection limits one output shape, while upstream data minimization, storage, logging, and authorization remain separate obligations. Test the projection without presenting it as a complete confidentiality boundary.

Key takeaways

  • A type annotation does not validate remote bytes
  • Verify shape, meaning, and presentation independently

Official references