react-native-screens Explained: What It Does and Why Your App Depends On It

What react-native-screens does under React Navigation, the Android MainActivity override you need, version limits in 4.x, enableFreeze vs detaching, and what 5.0 changes.

react-native-screens Explained: What It Does and Why Your App Depends On It

Open the package.json of almost any React Native app and react-native-screens is in there. Most developers never import it, never configure it, and could not tell you what it does. It pulls over six million downloads a week anyway, because React Navigation depends on it, and it is quietly responsible for whether your navigation feels native or feels like a web app in a phone-shaped box.

This is what it actually does, why the version you have matters, and the handful of situations where you will need to touch it directly.

Written against react-native-screens 4.27.0, current as of September 2026, with notes on the 5.0 alpha line.

The one-paragraph answer

React Native renders your entire navigation tree into one big view hierarchy. Every screen you have visited stays mounted, laid out and attached, just hidden behind the one on top. react-native-screens replaces those plain views with real native containers, UIViewController on iOS and Fragment on Android, so the operating system knows what a screen is and can detach the ones you cannot see.

That single change buys you three things: less memory and layout work for deep stacks, correct platform behaviour for things the OS owns (back gestures, status bar handling, screen reader focus, state restoration), and native transitions instead of JavaScript-driven ones.

The problem it solves

Picture a stack five screens deep. Without native screens, all five are in the hierarchy. Screen one is still laid out. Its FlatList is still a real list with real cells. Its images are still decoded in memory. Nothing about that is obvious from JavaScript, because from React’s perspective everything is fine: the component is mounted, that is all you asked for.

On iOS this shows up as memory pressure on older devices and as a growing shadow tree that makes every subsequent layout pass slightly more expensive. On Android it shows up as jank and the occasional crash after an Activity restart, because the view state that got persisted no longer matches what gets restored.

Native screens solves it by handing the problem to the platform, which has had a solution since before React existed. A UINavigationController knows how to keep one view controller visible and the rest detached. So does a FragmentManager. The library’s whole job is to make React Native’s reconciler produce those objects instead of nested UIViews.

You almost certainly already have it

React Navigation has shipped screens support since 2.14.0 and enables it automatically. If you installed @react-navigation/native, you got react-native-screens as a peer dependency, and every navigator you create already uses it. There is nothing to turn on.

# bare React Native
yarn add react-native-screens && npx pod-install

# Expo
npx expo install react-native-screens

iOS is fully autolinked and needs nothing else. Android has one caveat that is worth knowing before it bites you.

The Android caveat nobody reads until they get the crash report

Android does not persist view state consistently across Activity restarts, which produces crashes when the restored state does not line up with the fragments being recreated. The library’s fix is a fragment factory you install in MainActivity:

import android.os.Bundle
import com.swmansion.rnscreens.fragment.restoration.RNScreensFragmentFactory

class MainActivity : ReactActivity() {
  override fun onCreate(savedInstanceState: Bundle?) {
    supportFragmentManager.fragmentFactory = RNScreensFragmentFactory()
    super.onCreate(savedInstanceState)
  }
}

Two details that cost people time. It goes in MainActivity directly, not inside MainActivityDelegate. And it must run before super.onCreate(), because that is when the fragment manager starts restoring.

You will only see the crash in a specific scenario: don’t-keep-activities enabled, or the process killed in the background and the user returning to it. Which is to say you will never see it locally and you will see it in production. Add the override now.

Version compatibility, which is stricter than you think

The 4.x line tracks React Native closely, and one of those rows is a hard break rather than a recommendation:

screens version React Native Note
4.26.0+ 0.84.0+ Current line, 4.27.0 adds 0.87 support
4.25.0+ 0.82.0+ Legacy architecture no longer supported
4.19.0+ 0.81.0+ Paper supported down to 0.80
4.0.0+ 0.76.0+ Fabric became the default in 0.76

If you are still on the old architecture, 4.25.0 is the wall. There is no flag, no shim and no plan to bring Paper back. That is not the library being aggressive, it is where the whole ecosystem went when 0.76 made Fabric the default. But it does mean a New Architecture migration is now a prerequisite for a lot of routine dependency updates, not an optional performance project.

4.27.0 (August 7, 2026) adds React Native 0.87 support and fixes an iOS runtime crash in the process, so if you upgraded to 0.87 and saw something odd on launch, check this version first.

The three functions you might actually call

enableScreens

import { enableScreens } from 'react-native-screens';
enableScreens(false); // turn native screens OFF globally

You almost never want this. It exists as an escape hatch for debugging: if a rendering bug appears only in a navigator, flipping this off tells you in thirty seconds whether native screens is involved. Turn it back on.

detachInactiveScreens

A per-navigator prop rather than a global. Set it to false on a specific navigator when you need an offscreen screen to keep running, a video that should not pause or a map that should not tear down its tiles. It is the surgical version of enableScreens(false) and it is usually the right answer.

enableFreeze

import { enableFreeze } from 'react-native-screens';
enableFreeze(true);

Still opt-in and still labelled experimental. It uses React’s Suspense mechanism (via react-freeze) to stop offscreen parts of the tree re-rendering while keeping their state intact. Detaching stops the native views from being laid out; freezing stops the React components above them from doing work.

It is a genuine win on tab navigators where background tabs subscribe to something chatty. It also has a sharp edge: a frozen component does not re-render, so anything that assumed a render tick would happen while offscreen quietly stops happening. Polling intervals set up in useEffect keep running, because effects are not frozen, but derived state that depended on a render does not update until the screen comes back. Turn it on, then actually use the app for ten minutes before shipping it.

Native stack: the thing you are really getting

The most visible benefit of screens is createNativeStackNavigator. The JS stack from @react-navigation/stack draws headers and runs transitions in JavaScript. The native stack hands both to UINavigationController and Android’s fragment transactions.

import { createNativeStackNavigator } from '@react-navigation/native-stack';

Note the import path. There is an older react-native-screens/native-stack export that is deprecated and scheduled for removal. If your codebase still imports from there, that is a five-minute fix you should do before your next upgrade.

What the native stack gets right that the JS stack cannot: the iOS interactive back gesture behaves exactly like every other iOS app because it is the same gesture, large titles and search bars are real UINavigationBar features rather than reimplementations, transitions do not stutter when JavaScript is busy, and VoiceOver moves focus to the new screen’s title on push without you doing anything.

What you give up: header customisation is constrained by what the platforms expose, and the two platforms do not expose the same things. If your designer wants a header that is identical pixel for pixel on iOS and Android, you will spend the difference. Most teams decide platform-correct beats identical. Some do not, and the JS stack is a legitimate choice for them.

What arrived in the 4.x line worth knowing

The library has been shipping much more than plumbing lately:

  • Tabs went stable in 4.26.0 (July 2026). A real native tab bar primitive, which is what @react-navigation/bottom-tabs has needed for years to stop drawing its own.
  • Split navigation for iPad and large-screen layouts, now fully migrated from Swift to Objective-C across 4.26 and 4.27 (a maintenance decision, but it removed a Swift dependency from the iOS build).
  • FormSheet as an experimental standalone component on both platforms, with Android keyboard and animation coordination fixed in 4.26.0. If you have been fighting sheet-style modals, this is the direction of travel. We covered the current options in React Native modals done right.
  • ScrollToTopGuard, experimental in 4.27.0 on iOS, for the tap-status-bar-to-scroll-to-top behaviour that gets confused when several scroll views are in the hierarchy.

What 5.0 is going to change

The 5.0 alphas started in July 2026 and are at 5.0.0-alpha.2 as of this writing. The headline is a rewritten native stack, referred to in the changelogs as Stack v5, which moved out of the experimental sub-package and is now imported directly from react-native-screens. The old components are still exported and still supported, labelled legacy, until 5.0 goes stable with the new stack.

Recent alpha work has added liftOnScroll for the small header on Android and titleMenu support on iOS, which tells you where the focus is: parity with what the platforms actually offer, rather than a lowest common denominator.

Practical advice: do not put an alpha in a production app. Do build against it once if you maintain a navigation-adjacent library, because the legacy components have a stated end date.

Debugging the three problems people hit

A screen unmounts when I navigate away and loses its state. That is detaching working as designed. If the state matters, lift it out of the component, or set detachInactiveScreens={false} on that navigator.

My modal renders behind everything on iOS. Native screens changes what “on top” means. FullWindowOverlay exists exactly for this: it renders straight under the iOS Window, above the navigation controller’s hierarchy. Toasts, custom alerts and anything that must cover a native header belong in one.

Crashes on Android only after the app was backgrounded for a long time. That is the fragment factory override above, nine times out of ten. Reproduce it by enabling “Don’t keep activities” in developer options rather than waiting for the crash reports to accumulate.

Should you care, if it already works?

Mostly no, and that is the compliment. This library’s job is to be invisible. But it is worth knowing three things about it: that a version bump can force a React Native upgrade (4.25.0 and the legacy architecture), that the Android MainActivity override is not optional, and that detachInactiveScreens exists for the day a screen stops doing something offscreen and you cannot work out why.

Those three facts will save you more time than any performance tip you read this month.

FAQ

Do I need to install react-native-screens separately?

If you use React Navigation, it is a peer dependency you install alongside it. You do not need to import or configure anything for it to work.

Is react-native-screens enabled by default?

Yes. React Navigation enables it automatically. enableScreens(false) exists to turn it off, not on.

What is the difference between detaching and freezing?

Detaching removes the native views of offscreen screens from the hierarchy, saving native memory and layout work. Freezing stops the React components re-rendering, saving JavaScript work. Detaching is on by default. Freezing is opt-in via enableFreeze(true) and still experimental.

Does react-native-screens work with the legacy architecture?

Not from 4.25.0 onward. If your app has not migrated to the New Architecture, 4.24.x is your ceiling, and the same constraint is spreading across the ecosystem.

Which platforms does it support?

iOS, Android, tvOS, visionOS, Windows and Web.

Should I use the native stack or the JS stack?

Native stack, unless you need header customisation the platforms do not expose. The native stack gives you real platform gestures, real navigation bars and transitions that survive a busy JavaScript thread. Import it from @react-navigation/native-stack, not the deprecated react-native-screens/native-stack path.

Leave a Reply 0

Your email address will not be published. Required fields are marked *