When client-side entity normalization actually becomes necessary in large React Native apps

Published: (January 9, 2026 at 07:12 AM EST)
2 min read
Source: Dev.to

Source: Dev.to

Background

Over the past few years, while working on several React Native projects—different products, different teams—I kept encountering very similar symptoms.

As the apps grew, the same entities began to appear in more and more places:

  • feeds
  • detail screens
  • search results
  • notifications
  • background updates

Each new feature introduced small, reasonable decisions:

  • cache a list response
  • refetch on screen focus
  • merge partial updates
  • add derived selectors
  • manually sync data between screens

Individually, these choices made sense. Collectively, they were all trying to solve the same underlying problem. At some point it became clear this wasn’t accidental anymore.

When Normalization Felt Unnecessary

For a long time, entity normalization seemed unnecessary when the data:

  • belongs to a single screen
  • has a short lifecycle
  • isn’t reused elsewhere

In those cases, keeping the data close to the API response works perfectly fine, and normalization would mostly add ceremony without much payoff.

When Normalization Becomes Necessary

The problem started once data stopped being screen‑local. Libraries like Redux Toolkit or React Query are popular for good reasons, but their strength is also their breadth.

My core requirement was much narrower: reactive updates for shared data across screens, with stable identity.

  • MobX handled that part extremely well.

  • The rest of the architecture emerged later, following fairly standard clean‑code principles:

    • normalization to avoid duplicated entity instances
    • explicit relationships instead of nested DTO trees
    • lifecycle boundaries for long‑lived data
    • async orchestration to avoid race conditions

None of this was planned upfront. Over time, the codebase grew more in coordination logic than in features.

Data‑Lifecycle Challenges

Entities no longer belonged to a single screen. Without explicit rules, this led to:

  • memory growth with no clear eviction strategy
  • accidental retention through forgotten references
  • uncertainty around who actually “owns” the data

When lifecycle became an explicit concern, a real shift happened as these concerns were made explicit and composable:

  • garbage‑collection strategies
  • persistence
  • async control (cancel, retry, refresh)
  • integration boundaries

Treating them as pluggable layers clarified responsibilities.

Extracting the Approach

At that point, the structure stopped being tied to a single project. Extracting this approach into a small library wasn’t the original goal; it happened because the same structure kept reappearing across projects. It’s still very much an experiment.

If you’re curious, I extracted the approach into a small open‑source experiment:

https://github.com/nexigenjs/entity-normalizer

Open Questions

I don’t think there’s a single correct answer here. I’m curious how others approach this today:

  • When does client‑side entity normalization start paying off for you?
  • Where do you draw the line between server cache and domain entities?
  • How do you handle lifecycle and ownership of shared client‑side data?
Back to Blog

Related posts

Read more »

NgRx Toolkit v21

NgRx Toolkit v21 The NgRx Toolkit originates from a time when the SignalStore was not even marked stable. In those early days, community requests for various f...

How React works?

Component is the base React app is made of components. A component is just a JavaScript function that returns UI. javascript function App { return Hello ; } JS...