Have you ever noticed that when you navigate back to a screen in React Native, your useEffect() doesn’t run again?
It feels like your screen is cached — and that’s partly true.
Let’s break down what’s really happening.
🎯 1. What actually happens when you push() vs replace()
In React Navigation (and Expo Router, which uses it under the hood):
-
navigation.push('Screen')→ pushes a new instance of that screen onto the stack. -
navigation.replace('Screen')→ replaces the current screen with a new one (the old one unmounts). - When you go back, React Navigation just shows the previous screen again — it doesn’t remount it.
That’s why your useEffect(() => { ... }, []) only runs once — when the screen is first created.
🧠 2. Your screen is not cached — it’s still mounted
When you leave a screen using navigation.push(), the previous screen stays mounted in memory.
React Navigation keeps it alive so that going back is instant.
That means:
-
useEffect()with empty dependencies ([]) won’t run again. - Component state (e.g., form inputs) remains as it was.
-
cleanupfrom your effect won’t run either until the screen is unmounted.
If you want code to run every time the screen becomes visible again, you have to listen for focus events.
👀 3. Run code every time the screen comes into focus
You can use one of three approaches:
✅ Option 1: useFocusEffect
The most React-y way to handle this.
import { useFocusEffect } from '@react-navigation/native';
import { useCallback } from 'react';
useFocusEffect(
useCallback(() => {
console.log('Screen is focused');
return () => console.log('Screen lost focus');
}, [])
);
Runs every time your screen is focused or blurred — without remounting.
✅ Option 2: Focus listener with navigation.addListener
useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => {
console.log('Screen is focused again');
});
return unsubscribe;
}, [navigation]);
This is the more “manual” version — same result, different style.
✅ Option 3: useIsFocused
Perfect if you just need a quick boolean to trigger effects.
import { useIsFocused } from '@react-navigation/native';
const isFocused = useIsFocused();
useEffect(() => {
if (isFocused) {
console.log('Screen is visible');
}
}, [isFocused]);
🔁 4. Forcing a remount (when you really want a fresh screen)
If you do want your screen to reset or useEffect to run fresh every time you navigate back:
<Stack.Screen
name="MyScreen"
options={{ unmountOnBlur: true }}
/>
Now the screen will unmount when you leave and remount when you come back — so your normal useEffect will run again.
You can set this globally too:
<Stack screenOptions={{ unmountOnBlur: true }} />
🧩 5. If your effect depends on route params
Sometimes you just want your effect to re-run when the route data changes.
const { id } = route.params ?? {};
useEffect(() => {
fetchHouseDetails(id);
}, [id]);
This ensures that if you navigate to the same screen with a different id, your effect re-runs automatically.
⚡️ Quick summary
| Situation | Recommended Solution |
|---|---|
| You want code to run only once | useEffect(() => {}, []) |
| You want code to run every time the screen is visible |
useFocusEffect or useIsFocused
|
| You want the screen to reset completely |
unmountOnBlur: true or use replace()
|
| You want to handle new data | Add route params to your effect dependencies |
💡 Tip for Expo Router users
Everything above works the same — just import from "expo-router" instead of "@react-navigation/native".
Example:
import { useFocusEffect } from 'expo-router';
🧭 Takeaway
Your React Native screens aren’t “cached” — they’re just still mounted.
Understanding this helps you control when your effects run, so you can keep your app responsive and predictable.
If your logic needs to refresh every time the user returns, don’t rely on useEffect.
Use focus-aware hooks — they’re your navigation superpower. 🚀
Thanks for reading! If you found this helpful, give it a ❤️ and follow for more React Native + Expo tips.
Top comments (0)