Fast Refresh is a React Native feature that allows you to get near-instant feedback for changes in your React components. It’s an improvement over the previous Hot Reloading implementation.
What is Fast Refresh?
Fast Refresh is a feature that:
Preserves component state during edits
Only updates the code that changed
Catches errors during render and displays them in the app
Works with function components and Hooks
Provides immediate visual feedback
How It Works
When you save a file, Fast Refresh:
Detects the changed module through Metro bundler
Re-evaluates that module and its dependencies
Updates the app without losing component state
Shows a visual indicator that refresh occurred
HMR Client
Fast Refresh is powered by Metro’s Hot Module Replacement (HMR) system:
// Internal implementation from React Native source
HMRClient . enable ();
// Fast Refresh preserves state for:
// - useState
// - useReducer
// - useRef
// - Component instance variables
Enabling Fast Refresh
iOS
Open Developer Menu: Cmd + D (Simulator) or shake device
Enable “Fast Refresh”
The setting persists across app reloads
Android
Open Developer Menu: Cmd/Ctrl + M (Emulator) or shake device
Enable “Fast Refresh”
The setting persists across app reloads
Programmatically
import DevSettings from 'react-native/Libraries/Utilities/DevSettings' ;
// Trigger Fast Refresh
DevSettings . onFastRefresh ();
// Reload the entire app (loses state)
DevSettings . reload ( 'Manual reload' );
What Gets Refreshed?
Function Components
// ✅ Fast Refresh works - state is preserved
function Counter () {
const [ count , setCount ] = useState ( 0 );
return (
< View >
< Text > Count: { count } </ Text >
< Button title = "+" onPress = { () => setCount ( count + 1 ) } />
</ View >
);
}
When you edit this component, the count state is preserved.
Class Components
// ✅ Fast Refresh works with limitations
class Counter extends React . Component {
state = { count: 0 };
render () {
return (
< View >
< Text > Count: { this . state . count } </ Text >
< Button
title = "+"
onPress = { () => this . setState ({ count: this . state . count + 1 }) }
/>
</ View >
);
}
}
Class component state is preserved, but editing lifecycle methods may require a full reload.
State Preservation Rules
Preserved State
State is preserved when you edit:
Component render logic
Event handlers
Effects and their dependencies
Styles
Non-exported helper functions
// ✅ Editing this preserves state
function TodoList () {
const [ todos , setTodos ] = useState ([]);
// Editing this handler preserves todos state
const addTodo = ( text ) => {
setTodos ([ ... todos , { id: Date . now (), text }]);
};
return (
< View >
{ todos . map ( todo => (
< Text key = { todo . id } > { todo . text } </ Text >
)) }
</ View >
);
}
Reset State
State resets when you edit:
Exports from the module
Hook call order
Hook types (useState → useReducer)
Component signature changes
// ❌ Adding a new prop resets state
function Counter ({ initialValue }) { // <- New prop added
const [ count , setCount ] = useState ( initialValue );
// ...
}
// ❌ Changing Hook call order resets state
function Counter () {
if ( condition ) {
useState ( 0 ); // <- Conditional Hook
}
// ...
}
Error Recovery
Fast Refresh catches errors and displays them:
Syntax Errors
// Syntax error
function App () {
return (
< View
<Text>Hello</Text> { /* Missing closing > */ }
</View>
);
}
Fast Refresh shows:
The error message
File and line number
Code frame with the error
Fix the error and save - the app recovers automatically!
Runtime Errors
// Runtime error
function UserProfile ({ user }) {
return < Text > { user . name } </ Text > ; // user might be undefined
}
Fast Refresh displays:
Red error screen
Stack trace
Error boundary information
The error is shown in LogBox:
import { LogBox } from 'react-native' ;
// Errors are caught and displayed
// No need to manually configure error boundaries for development
Hooks Support
Fast Refresh has first-class support for Hooks:
useState
// ✅ State preserved across edits
function Form () {
const [ name , setName ] = useState ( '' );
const [ email , setEmail ] = useState ( '' );
// Edit this render logic without losing state
return (
< View >
< TextInput value = { name } onChangeText = { setName } />
< TextInput value = { email } onChangeText = { setEmail } />
</ View >
);
}
useEffect
// ✅ Effect re-runs when dependencies change
function Timer () {
const [ seconds , setSeconds ] = useState ( 0 );
useEffect (() => {
const interval = setInterval (() => {
setSeconds ( s => s + 1 );
}, 1000 );
return () => clearInterval ( interval );
}, []); // Edit this and effect re-runs
return < Text > { seconds } s </ Text > ;
}
Custom Hooks
// ✅ Custom Hooks update with Fast Refresh
function useCounter ( initialValue = 0 ) {
const [ count , setCount ] = useState ( initialValue );
const increment = () => setCount ( count + 1 );
const decrement = () => setCount ( count - 1 );
return { count , increment , decrement };
}
function Counter () {
const { count , increment , decrement } = useCounter ();
return (
< View >
< Text > { count } </ Text >
< Button title = "+" onPress = { increment } />
< Button title = "-" onPress = { decrement } />
</ View >
);
}
Limitations
Module Exports
Changing exports causes a full reload:
// ❌ Changing this causes full reload
export const API_URL = 'https://api.example.com' ;
// ✅ Keep constants internal when possible
const API_URL = 'https://api.example.com' ;
export function App () {
// Use API_URL here
}
Class Components
// ⚠️ Limited support - may require full reload
class MyComponent extends React . Component {
// Editing lifecycle methods may not work
componentDidMount () {
// Changes here might need full reload
}
}
Anonymous Functions
// ❌ Fast Refresh can't track anonymous exports
export default () => < View > < Text > Hello </ Text > </ View > ;
// ✅ Use named functions
export default function App () {
return < View >< Text > Hello </ Text ></ View > ;
}
Higher-Order Components
// ⚠️ HOCs may lose state
const Enhanced = withSomeLogic ( MyComponent );
// ✅ Prefer Hooks for composition
function MyComponent () {
const logic = useSomeLogic ();
// ...
}
Tips for Best Experience
1. Use Function Components
// ✅ Recommended
function Welcome ({ name }) {
return < Text > Hello, { name } ! </ Text > ;
}
// ⚠️ Works but less optimal
class Welcome extends React . Component {
render () {
return < Text > Hello, { this . props . name } ! </ Text > ;
}
}
2. Keep Components Small
// ✅ Fast Refresh is faster with smaller components
function UserCard ({ user }) {
return (
< View >
< Avatar user = { user } />
< UserInfo user = { user } />
< UserActions user = { user } />
</ View >
);
}
3. Use Named Exports
// ✅ Fast Refresh can track these
export function Button () { /* ... */ }
export function Input () { /* ... */ }
// ❌ Avoid
export default () => { /* ... */ };
// ✅ Extract logic to custom Hooks
function useUserData ( userId ) {
const [ user , setUser ] = useState ( null );
useEffect (() => {
fetchUser ( userId ). then ( setUser );
}, [ userId ]);
return user ;
}
function UserProfile ({ userId }) {
const user = useUserData ( userId );
return user ? < Text > { user . name } </ Text > : < ActivityIndicator /> ;
}
Troubleshooting
Try these solutions:
Check that Fast Refresh is enabled in Dev Menu
Restart Metro bundler: npm start -- --reset-cache
Reload the app: shake device → “Reload”
Check for syntax errors in your code
Verify you’re using function components for best results
Common causes:
Changing component props or signature
Modifying exported constants
Changing Hook call order
Using anonymous function components
HOC wrapping is changing
Full reload happening instead of Fast Refresh
This happens when:
You edit a file that exports non-component values
You change class component lifecycle methods
You modify files outside of components (utils, constants)
Circular dependencies are detected
Fast Refresh is designed to be fast:
Updates typically complete in < 100ms
Only changed modules are re-evaluated
No full app reload required
State preservation eliminates navigation overhead
Comparison
Method Time State Preserved Fast Refresh ~100ms ✅ Yes Hot Reload (legacy) ~500ms ⚠️ Sometimes Full Reload ~5-10s ❌ No
Developer Settings Integration
Fast Refresh integrates with React Native DevSettings:
import DevSettings from 'react-native/Libraries/Utilities/DevSettings' ;
// Add custom menu item
DevSettings . addMenuItem ( 'Reset App State' , () => {
// Custom reset logic
DevSettings . reload ();
});
// Trigger Fast Refresh programmatically
DevSettings . onFastRefresh ();
Next Steps
Debugging Learn about debugging tools and techniques
Metro Configure Metro bundler for your project