Running Screen-Level Side Effects with React Navigation's useFocusEffect
Use React Navigation's useFocusEffect to refresh screens when users return, with cleanup guards to prevent stale data and duplicate side effects.
11 Jul 2025, 17:49 UTC

The problem: your screen only updates when it first mounts
Imagine a task list screen in a React Native app. The user opens it, the list loads, then they tap a task to view its details. When they press the hardware back button and return to the list, newly completed tasks from the detail screen should appear. But if you wired the data fetch to React.useEffect with an empty dependency array, the fetch ran once at mount and never again. The list looks stale until the user fully restarts the app.
useFocusEffect from @react-navigation/native solves this by tying a side effect to navigation focus instead of component mount. When the screen becomes the active screen in a navigator, the effect runs. When the user navigates away, the cleanup function runs. If they return, the effect runs again.
Where useFocusEffect works, and where it does not
This hook only works inside screens rendered by a React Navigation navigator that is nested inside NavigationContainer. A standalone presentational component cannot use it because it has no navigation focus events to listen to. It is available in React Navigation v5 and v6, but import locations and peer dependencies differ between major versions, so check package.json before copying examples.
Check your installed version
grep '@react-navigation/native' package.json
Run this in your project root. No special permissions are required. The command only reads the file. Expected output is a line like @react-navigation/native: ^6.x.x. If you see a v5 range, consult the v5 documentation because navigator behavior and peer requirements can differ.
Worked example: refresh a list when the screen regains focus
This example assumes React Navigation v6 and a stack navigator. The screen fetches items whenever it becomes focused, and cancels the in-flight request when the user navigates away.
import { useFocusEffect } from '@react-navigation/native';
import { useCallback, useRef } from 'react';
import { ActivityIndicator, FlatList, View } from 'react-native';
export default function TaskListScreen() {
const isMountedRef = useRef(false);
useFocusEffect(
useCallback(() => {
isMountedRef.current = true;
fetchTasks().then((tasks) => {
if (isMountedRef.current) {
setTasks(tasks);
}
});
return () => {
isMountedRef.current = false;
};
}, [])
);
// ... render list
}
The cleanup function sets isMountedRef.current to false. If the user navigates away before the promise resolves, the component ignores the result when it arrives. Without that guard, a stale response could overwrite newer data after the user has already moved on.
Dependency arrays: do not omit them unless you really mean to run on every focus
If you call useFocusEffect without a dependency array, the effect executes on every focus event, even when nothing changed. That is useful for analytics page views, but expensive for data fetches. Wrap the effect body in useCallback and pass a dependency array so React Navigation only re-runs the effect when those dependencies change.
Trade-offs and limitations
- Navigator boundary: The hook silently does nothing outside a navigator. If you move the component to a modal or a standalone screen later, the effect stops running with no error.
- Focus delay: Heavy work inside the effect can delay the focus transition and make navigation feel sluggish. Keep the effect lightweight, or defer heavy work to a background task.
- Duplicate work: Forgetting cleanup leaves listeners or intervals active after blur, which can cause duplicate analytics events or memory leaks.
- Version drift: Behavior is consistent across v5 and v6, but peer packages and navigator APIs are not. Confirm your major version before adopting patterns from blog posts or Stack Overflow.
How to verify it is working
- Add
console.log('focus effect ran')at the start of the effect andconsole.log('focus effect cleanup')at the start of the returned cleanup function. - Run the app in a simulator or on a device.
- Navigate to the screen, then navigate away, then navigate back.
- Inspect the logs. You should see the effect run on initial focus, cleanup on blur, and the effect run again on return.
If you do not see cleanup on blur, the component is likely rendered outside NavigationContainer or the hook is being used in a non-screen component.
Closing
Use useFocusEffect when a screen needs to react to visibility, not just existence. It is the right tool for refreshing data, logging screen views, or starting and stopping subscriptions tied to the user's current screen. Keep the effect small, always return cleanup when you start something asynchronous, and verify with logs before shipping.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.