mrkeyoor.com_
Sat 08 Aug 17:40 UTC
npmWeb Frontendupdated 08 Aug 2026

react-freeze

react-freeze exports one React component that prevents a mounted subtree from rendering while a boolean is true. It uses a Suspense boundary and a never-resolving thenable, swaps the visible subtree for an optional placeholder, and keeps the same DOM or React Native views and component state for later. Its intended use is fully hidden navigation screens whose state and scroll position should survive while unnecessary reconciliation stops.

Verdict

react-freeze is a precise performance tool for already-hidden mounted screens, not a general memoization primitive. Measure first, keep lifecycle work outside the frozen boundary, and prefer navigation-native support when it already exists.

API stability5/5The public API is one `Freeze` component with required `freeze`, optional `placeholder`, and children. Version 1.0.4 packages both module formats and TypeScript declarations, while the source remains a tiny Suspense boundary around a fixed thenable. There is almost no surface to churn. The larger compatibility risk comes from React and navigation behavior around this unusual Suspense use, not from library option changes.
Docs3/5The README clearly explains retained state and native views, direct usage, the two props, React and React Native minimums, navigation integration, Redux behavior, layout changes, visible-subtree hazards, render side effects, and the React Native Debugger profiler problem. It has no separate API site, current React Navigation guide, lifecycle guide, SSR discussion, or extensive test recipes, so important production questions remain empirical.
Maintenance3/5The repository was pushed in June 2026 and has 1,652 stars with 17 open issues and pull requests, so it is not abandoned. The latest npm release is still 1.0.4 from February 2024, and the README's primary navigation instructions target React Navigation 5 and 6 with react-native-screens 3.9.0. A tiny stable component needs few releases, but its surrounding ecosystem has moved considerably.
Ecosystem3/5react-freeze recorded 6,687,874 downloads in the latest measured week, has first-party ties to Software Mansion's React Native Screens work, and supports both React DOM and React Native. The package itself intentionally exports only one component, so there is little plugin ecosystem. High downloads likely reflect transitive navigation use as much as teams choosing its direct API.

Use it if

  • A measured performance problem comes from hidden but mounted React or React Native screens re-rendering
  • You must preserve component state, native view instances, scroll position, input state, and loaded images
  • A subtree is guaranteed to be non-interactive and visually covered whenever it is frozen
  • You use a supported React 17 or newer runtime and can test Suspense behavior across navigation transitions
Skip it if

Setup reality

Install `react-freeze`; version 1.0.4 declares React 17 or newer as its only peer and Node 10 or newer for tooling, with CommonJS, modern module, and bundled TypeScript declaration entries. Direct use is one `<Freeze freeze={boolean}>` boundary. The simplicity hides behavioral work. When frozen, children do not render and the boundary shows `placeholder`, defaulting to null, but the existing DOM or native view instances and React state remain for defrost. Only freeze content that is completely hidden. If sibling layout depends on its box, supply a correctly sized placeholder or use absolute positioning. State changes still happen and Redux selectors still execute; the latest state appears in one render after defrost. Freezing does not unmount the subtree, so it is not a lifecycle signal for stopping sockets, timers, media, polling, or native work. Stop that work separately before freezing. Render-time side effects may be skipped across updates, which the README lists as a way behavior can change. For React Native navigation, the documented path requires React Native 0.64 or later, React Navigation 5 or 6, react-native-screens 3.9.0, an iOS pod install, and `enableFreeze(true)`; it intentionally leaves the top two stack screens unfrozen for swipe-back transitions. That recipe is dated relative to current navigation releases, so verify the navigation package's own current guidance before adding react-freeze directly. Profile on target devices first, because an extra Suspense boundary is only justified by measured hidden-screen render cost.

Patterns

Freeze a subtree only while it is hiddenfreeze-hidden-subtree

import { Freeze } from 'react-freeze';

function HiddenPanel({ hidden, children }) {
  return (
    <Freeze freeze={hidden}>
      <section aria-hidden={hidden}>{children}</section>
    </Freeze>
  );
}

When frozen, the section is replaced by null, so this is appropriate only when the user should not see or interact with it.

Keep layout space with a placeholderpreserve-layout-placeholder

<Freeze
  freeze={!visible}
  placeholder={<div style={{ width: 320, height: 240 }} aria-hidden="true" />}
>
  <ExpensiveChart />
</Freeze>

The README warns that the default null fallback can reflow siblings; the placeholder must match the layout you need to preserve.

Freeze mounted inactive tab panelsfreeze-inactive-tabs

function Tabs({ activeId, tabs }) {
  return tabs.map((tab) => (
    <Freeze key={tab.id} freeze={tab.id !== activeId}>
      <div role="tabpanel" aria-label={tab.label}>
        <tab.Component />
      </div>
    </Freeze>
  ));
}

This retains every tab's state and native nodes, which saves rerenders but also retains their memory.

Freeze only deeply covered stack screensfreeze-covered-stack-screens

function StackScreen({ depthFromTop, children }) {
  const covered = depthFromTop > 1;
  return <Freeze freeze={covered}>{children}</Freeze>;
}

The documented navigation integration leaves the top and second screen active so swipe-back can reveal the previous screen correctly.

Enable react-native-screens integrationenable-native-navigation-freeze

import { enableFreeze } from 'react-native-screens';

enableFreeze(true);

The react-freeze README documents this for React Navigation 5 or 6 and react-native-screens 3.9.0; check current screens documentation before copying it into a newer stack.

Retain a hidden form without unmountingpreserve-form-state

function DraftStep({ active }) {
  return (
    <Freeze freeze={!active}>
      <div style={{ display: active ? 'block' : 'none' }}>
        <DraftForm />
      </div>
    </Freeze>
  );
}

Input and component state survive defrost, but so does memory; do not keep an unlimited number of completed forms mounted.

Stop polling separately from render freezingstop-work-before-freeze

function ScreenSlot({ active }) {
  useEffect(() => {
    if (!active) return;
    const timer = setInterval(refresh, 30000);
    return () => clearInterval(timer);
  }, [active]);

  return <Freeze freeze={!active}><Screen /></Freeze>;
}

Keep this effect above the boundary. Frozen children do not receive the render that would let their own effect observe `active=false`.

Wait for a navigation transition before freezingfreeze-after-transition

function RouteSlot({ focused, transitionRunning, children }) {
  const shouldFreeze = !focused && !transitionRunning;
  return <Freeze freeze={shouldFreeze}>{children}</Freeze>;
}

Freezing a screen that is still visible during an animation replaces it with the fallback and can create a flash or broken gesture.

Avoid sibling reflow for stacked screensfreeze-absolutely-positioned-screen

<div style={{ position: 'absolute', inset: 0, visibility: active ? 'visible' : 'hidden' }}>
  <Freeze freeze={!active}>
    <Screen />
  </Freeze>
</div>

Absolute positioning matches the README's recommended case where removing frozen content does not reposition unfrozen siblings.

Test that updates appear after defrosttest-deferred-render

const { rerender, getByText, queryByText } = render(
  <Freeze freeze={false}><Counter value={1} /></Freeze>,
);
expect(getByText('1')).toBeTruthy();
rerender(<Freeze freeze={true}><Counter value={2} /></Freeze>);
expect(queryByText('2')).toBeNull();
rerender(<Freeze freeze={false}><Counter value={2} /></Freeze>);
expect(getByText('2')).toBeTruthy();

State and props can change while frozen, but the subtree renders the latest values only after the boundary defrosts.

Alternatives

PackageRegistryPick it when
react-native-screensnpmReact Native navigation should manage inactive native screens and its built-in freeze integration
react-activationnpmA web app needs keep-alive activation and deactivation semantics around cached routes
react-keep-alivenpmYou need a component cache that preserves web route state across removal from the visible tree