In a React Native app where every save causes a full reload instead of a Fast Refresh, how do you find and fix the cause?
answer
- updates bubble up the importers
- boundary: module exporting only components
- no boundary before the entry file
- import cycles force a reload
- changed exports invalidate a boundary
basics
~20 sFast Refresh walks up the edited module's importers looking for component-only modules. It reloads fully when a path reaches the entry file without one, when it meets an import cycle, or when a boundary's exports change. Fix the import graph, not Fast Refresh.
solid answer
~40 sA full reload on save is Metro's runtime choosing to reload, so I trace the edited module's importers. Updates bubble up until each path hits a **refresh boundary**, a module that exports only React components. The runtime reloads when a path climbs to the entry file without one, typically because a store, API client or setup module imports something from a component file; when the walk meets an import cycle; or when a boundary's exports changed shape and its importers are not boundaries. The fixes are structural: keep component files exporting only components, move shared constants and config into their own modules, and break cycles by extracting the shared piece. A "Fast Refresh disconnected" message is a different problem, a lost Metro connection.
code
typescript · 7 lines// setup.ts, imported by the entry file before the app registers
// Importing from a component file makes every edit to OnboardingForm.tsx reload the app.
import { STEP_TITLES } from './OnboardingForm';
// Fix: import it from a module that holds only data
// import { STEP_TITLES } from './onboardingSteps';
export const analyticsSteps = STEP_TITLES.length;go deeper
Recall that Fast Refresh reloads the app when an edited file is used from outside React components.
Explain refresh boundaries and how updates bubble up through importers until they reach one or the entry file.
Diagnose it methodically: trace importers, spot mixed-export component files, import cycles and changed exports, then fix the module structure.
Treat a codebase whose edits constantly reload as structural debt, and set module conventions that keep the team's dev loop fast.
## The symptom A developer on a React Native app reports that **every save reloads the whole app**: the splash appears, navigation returns to the first screen, and the onboarding form they are styling is empty again. Fast Refresh is enabled in the Dev Menu. Nothing is broken in the usual sense — the runtime is choosing a full reload on purpose. The job is to find which rule it is following. ## How Metro's runtime decides When an update arrives, Metro's module runtime walks **up** the import graph from the edited module: 1. If a module on the path exports only React components, it is a **refresh boundary**: that path stops there. 2. Otherwise it keeps climbing through that module's importers. 3. If a path reaches a module with **no importers** — the entry file — without meeting a boundary, the runtime gives up and reloads (its internal reason is "No root boundary"). 4. If the walk finds a **dependency cycle**, it reloads ("Dependency cycle"). 5. After re-running a boundary, if its **exports changed shape** — an export added, removed or renamed, or a class turned into a function — the boundary is invalidated; its importers must all be boundaries too, or the runtime reloads ("Invalidated boundary", or "No longer a boundary" when the module now exports something that is not a component). ## A diagnosis checklist - **Who imports the file you are editing?** Look for any importer that is not a component module: a store, an API client, a navigation setup file, a background handler registered in the entry file. - **Does the file export more than components?** A component file that also exports a constant becomes a pass-through; if one of that constant's consumers leads to the entry file, every edit reloads. - **Is there an import cycle?** Two modules that import each other, often a screen and a helper, force a reload for edits along that cycle. - **Did the edit change the exports?** Adding or renaming an export in a component file invalidates the boundary; if its importers are not boundaries, the app reloads. That one is expected and goes away on the next edit. - **Does it happen for every file or one?** One file points at its import graph; every file points at something global — for instance the app root being imported by non-component code. ## Typical fixes | Cause | Fix | |---|---| | a component file also exports config used by setup code | move the config to its own module | | setup module imports a component file for a constant | import the constant from a shared module instead | | import cycle between a screen and a helper | extract the shared piece into a third module | | root component file exports extra helpers | keep `App.tsx` exporting only the component | ## Related signals that are not this - **"Fast Refresh disconnected. Reload app to reconnect."** means the app lost its connection to Metro; edits are not arriving at all, which is different from arriving and forcing a reload. - **State lost without a splash screen** is a remount, not a reload: a class component, changed Hooks, `// @refresh reset` or a render error. - **An edit that does not appear at all** can be stale bundler output; Metro's cache is a separate subject. ## Why it is worth fixing A full reload on every save throws away navigation position and form input, so every styling tweak on step 3 of the onboarding flow costs a trip through steps 1 and 2. Across a team this is a real productivity cost, and the fix is almost always a small change to module structure rather than to Fast Refresh.
- Why does adding a new export to a component file sometimes cause a single full reload?After re-running a refresh boundary, Metro's runtime compares its exports with the previous version. If they changed shape, the boundary is invalidated and its importers must also be boundaries; if one is not, the runtime reloads. The next edit, with unchanged exports, refreshes normally.
- How do you tell a full reload apart from a remount while debugging lost state?A full reload re-runs the whole bundle: the app returns to its first screen and module-level state resets. A remount re-creates components with fresh state but does not re-run the bundle; for a class component, changed Hooks or `// @refresh reset` it is limited to the edited components, while a render error without an error boundary remounts the app from the root.
saying these in an interview costs you the question
- Full reloads on save mean Fast Refresh is turned off
- Reinstalling the app fixes Fast Refresh falling back to reloads
- Any module with a React import counts as a refresh boundary
- Import cycles are harmless for Fast Refresh
- Fast Refresh disconnected means a boundary problem in your code