DEV Community

Tepol
Tepol

Posted on

React Native Tab View

Listen, yesterday I was messing around with React Native Tab View (app) and ran into something that looked like a random performance glitch but turned out to be completely predictable once I slowed down and looked properly.

The symptom was simple: brutal lag and frame drops when switching tabs. Not always, but consistently enough to make the app feel… cheap. Especially on older iPhones and even on a mid-range Android device. Swiping between tabs stuttered, and sometimes the content would flash blank for a split second before rendering.

At first I assumed it was just the emulator being slow. That was my first mistake.

What I did first (and why it didn’t help)

I started by testing on a simulator. Laggy. Then I blamed hot reload. Restarted Metro, cleared cache with --reset-cache, rebuilt the app. Slight improvement, but the jank was still there.

Then I thought maybe my tab scenes were too heavy, so I removed some images and temporarily mocked out API calls. Still not smooth.

At that point I began suspecting something deeper in how React Native Tab View handles rendering and scene mounting.

For reference, this is the library I’m talking about:
https://www.npmjs.com/package/react-native-tab-view

And if you’re looking for general React Native docs, they’re here:
https://reactnative.dev/docs/getting-started

What I realized

The key thing about React Native Tab View is that by default it renders all scenes upfront unless you configure it otherwise. That means if you have three tabs, and each tab loads a heavy component tree, they’re all mounting immediately.

In my case, each tab had:

  • A FlatList with ~50 items
  • Image thumbnails
  • Some derived state
  • A couple of memoized selectors

Individually fine. Together? Not so fine.

What made it worse is that I wasn’t using lazy loading. So even if the user never swiped to the third tab, it was already rendered and sitting there in memory.

Once I looked at it through that lens, the lag made sense. It wasn’t the animation that was slow — it was React reconciling too much stuff during tab transitions.

What actually helped

Two things made a dramatic difference.

First, I enabled lazy loading:

<TabView
  lazy
  renderScene={renderScene}
  ...
/>
Enter fullscreen mode Exit fullscreen mode

That alone stopped off-screen tabs from mounting immediately.

Second, I wrapped heavy tab components in React.memo and made sure props were stable. I found that one of my inline arrow functions was causing re-renders on every swipe because the reference kept changing.

After those changes, the swipe animation became noticeably smoother. Not perfect, but “production-ready smooth.”

I also checked how the library recommends optimizing scenes in their examples and discussions. While digging, I found this page useful — the resource I used:
https://b3netmedia.com/developer/37356-react-native-tab-view.html

It helped confirm that lazy rendering and memoization are not “micro-optimizations” here — they’re expected usage patterns if your scenes aren’t trivial.

One more subtle issue

There’s also a prop called lazyPreloadDistance. By default it preloads adjacent tabs. That means even with lazy enabled, the next tab may render earlier than you expect.

Setting:

lazyPreloadDistance={0}
Enter fullscreen mode Exit fullscreen mode

made behavior more predictable. Tabs only mounted when actually navigated to.

On iOS especially, that reduced initial CPU spikes.

Testing on real devices

Here’s the part I almost skipped — testing on actual hardware.

Simulators exaggerate some performance problems and hide others. When I ran the updated build on a physical iPhone, the difference was obvious. Before optimization, there was a visible hitch when swiping quickly back and forth. After optimization, the transition stayed within 60fps most of the time.

On Android, I also enabled the performance monitor (Cmd + D → Show Perf Monitor) and watched the JS and UI thread FPS. The drops aligned exactly with scene re-renders.

So it wasn’t guesswork anymore — it was measurable.

Why this happens specifically with tab views

Tab views feel lightweight conceptually, but technically they’re coordinating:

  • Gesture handlers
  • Animated values
  • Scene mounting/unmounting
  • React reconciliation
  • Possibly network requests

If even one tab triggers a heavy synchronous calculation during render, the animation thread suffers.

React Native’s architecture makes it especially important to avoid unnecessary re-renders during gestures. Anything synchronous inside render or effect hooks can become visible as UI stutter.

What I’d do differently next time

Honestly, I wouldn’t scaffold tab screens with full production logic from day one. I’d start with lightweight placeholders, measure performance, and then gradually layer in complexity.

And I’d enable lazy immediately instead of treating it as an optional tweak.

Quick checklist for future me

  • Enable lazy on TabView.
  • Consider setting lazyPreloadDistance={0} if scenes are heavy.
  • Wrap tab scenes in React.memo.
  • Avoid inline functions that change on every render.
  • Test on a real device early, not just a simulator.

After those changes, the app felt completely different. Same UI, same logic — just structured in a way that doesn’t fight the rendering model.

It’s funny how performance issues often look mysterious until you realize they’re just side effects of default behaviors. React Native Tab View isn’t broken. It just assumes your scenes are reasonably light unless you tell it otherwise.

Anyway, figured I’d share while it’s still fresh in my head. If you ever see weird tab lag in a React Native project, chances are it’s not the animation system — it’s what you’re rendering behind it.

Top comments (0)