The Development Loop
Fast Refresh, reloading, the dev menu and the red error screen: the loop you spend every working day inside.
npx expo start # then r to reload, m for the dev menu, j for DevToolsThe loop is: save a file, look at the device. Metro rebuilds the module you changed and Fast Refresh re-renders it in place, so the screen keeps what it was holding: the text in a field, how far a list is scrolled, a counter's value.
When state survives
If the module you edit exports only components, Fast Refresh updates that module and re-renders it. If the module also exports something that is not a component, that module and every module importing it re-run, and state is lost. A file imported from outside the component tree forces a full reload. State is kept only while your hooks stay in the same order with the same arguments. Dependency lists are ignored during a refresh, so effects re-run every time.
Reloading and the dev menu
Press r in the terminal to reload, m to open the dev menu on the device, j for React Native DevTools. On the device: cmd + d in the iOS Simulator, cmd + m or ctrl + m in the Android emulator, or shake a real phone. The menu holds Reload, the element inspector, the performance monitor and DevTools.
The red screen
It is information, not a crash report. It gives the message, the component stack and the source frames, and tapping a frame opens that file. A red screen caused by a syntax error clears itself when you fix the file.
Example
import { useState } from "react";
import { Button, StyleSheet, Text, View } from "react-native";
export default function Counter() {
// useState gives a component a value it keeps between renders, plus a
// function that replaces it. Stage 3 covers it properly.
const [count, setCount] = useState(0);
return (
<View style={styles.screen}>
<Text style={styles.count}>{count}</Text>
{/* Tap a few times, then edit the title below and save: the number
stays where it is, because this module exports only a component. */}
<Button title="Add one" onPress={() => setCount(count + 1)} />
</View>
);
}
const styles = StyleSheet.create({
screen: {
flex: 1,
alignItems: "center",
justifyContent: "center",
gap: 16,
},
count: { fontSize: 48, fontWeight: "700" },
});
When to use it
- Keep a half-filled form on screen while restyling the component that renders it.
- Work out why a saved change did not appear, and whether the fix is a reload.
- Turn a red error screen into a file and a line number instead of guessing.
More examples
The edit that costs you your state
On screen: one line of text reading "at most 50 items". The lesson is the export above it. Move constants, helpers and types into their own files and keep component files exporting components only; that one habit is the difference between Fast Refresh preserving state and resetting the screen on every save.
// This module exports a component AND a plain value. Editing anything in
// it re-runs the module and every module importing it, so the state in
// those components is thrown away.
import { StyleSheet, Text, View } from "react-native";
export const MAX_ITEMS = 50; // a non-component export
export default function Limit() {
return (
<View style={styles.box}>
<Text>at most {MAX_ITEMS} items</Text>
</View>
);
}
const styles = StyleSheet.create({
box: { padding: 16 },
});
Reading the first red screen most people see
Worth causing on purpose once, so the screen becomes readable rather than alarming: the top line is the message, below it the component stack tells you which of your components rendered the offending tree, and the frames under that are the files to open. Delete the bare string, save, and the session carries on without a reload.
import { Text, View } from "react-native";
export default function Broken() {
return (
<View>
{/* Wrong. A bare string inside a View compiles, then throws:
"Text strings must be rendered within a <Text> component".
The component stack on the red screen names Broken. */}
Hello
{/* Right. */}
<Text>Hello</Text>
</View>
);
}
Every way to reach the dev menu
Use the terminal keys when the device is a simulator you are not clicking in, and the on-device gestures when you are testing on a real phone in your hand. The adb line is the one that saves you when a phone's shake gesture is unreliable or the device is mounted in a rig.
npx expo start
# In the terminal:
# r reload the app
# m open the dev menu on any connected device
# j open React Native DevTools (Hermes)
# shift + m more device commands, including the performance monitor
# On the device itself:
# iOS Simulator cmd + d
# Android emulator cmd + m on macOS, ctrl + m on Windows and Linux
# physical phone shake it
# Android over USB adb shell input keyevent 82
Discussion