Skip to main content
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:
  1. Detects the changed module through Metro bundler
  2. Re-evaluates that module and its dependencies
  3. Updates the app without losing component state
  4. 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

  1. Open Developer Menu: Cmd + D (Simulator) or shake device
  2. Enable “Fast Refresh”
  3. The setting persists across app reloads

Android

  1. Open Developer Menu: Cmd/Ctrl + M (Emulator) or shake device
  2. Enable “Fast Refresh”
  3. 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 () => { /* ... */ };

4. Extract Complex Logic

// ✅ 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:
  1. Check that Fast Refresh is enabled in Dev Menu
  2. Restart Metro bundler: npm start -- --reset-cache
  3. Reload the app: shake device → “Reload”
  4. Check for syntax errors in your code
  5. Verify you’re using function components for best results
Common causes:
  1. Changing component props or signature
  2. Modifying exported constants
  3. Changing Hook call order
  4. Using anonymous function components
  5. HOC wrapping is changing
This happens when:
  1. You edit a file that exports non-component values
  2. You change class component lifecycle methods
  3. You modify files outside of components (utils, constants)
  4. Circular dependencies are detected

Performance Impact

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

MethodTimeState 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

Build docs developers (and LLMs) love