Using React Navigation's useFocusEffect to Manage Screen-Focus Side Effects
Learn how React Navigation's useFocusEffect hook lets you start and stop subscriptions, animations, or data fetching exactly when a screen gains or loses focus, with a concrete NetInfo example and tips to avoid common pitfalls.
20 Oct 2025, 09:07 UTC

Problem: Wasted resources on hidden screens
In a React Native app, a screen that is not currently visible may still be running timers, subscriptions, or animations. If these are not stopped when the user navigates away, they consume CPU and battery and can cause memory leaks.
Thesis: useFocusEffect starts and stops work exactly when a screen gains or loses focus
The useFocusEffect hook from @react-navigation/native runs a callback whenever the screen becomes focused and expects a cleanup function that runs when the screen loses focus or the component unmounts. It replaces the older didFocus/willFocus navigation event listeners with a hook-style API that fits React's lifecycle.
How it works
- The callback receives no arguments; any values it needs must be captured from the component scope, typically via
useCallbackoruseRef. - The hook must return either a cleanup function or nothing. Returning anything else triggers a warning and can leave resources dangling.
- The callback re-runs on every focus change, including when a modal overlay appears, so heavy work should be debounced or throttled.
Worked example: Starting and stopping a NetInfo subscription
Suppose you want to show the current network type on a screen and stop listening when the screen is hidden. Run the install command in your project root (no elevated permissions needed):
npm install @react-navigation/native @react-native-community/netinfo
# or: yarn add @react-navigation/native @react-native-community/netinfo
Then create the screen component:
import React, { useState, useCallback } from 'react';
import { View, Text } from 'react-native';
import { useFocusEffect } from '@react-navigation/native';
import NetInfo from '@react-native-community/netinfo';
function NetworkStatusScreen() {
const [networkState, setNetworkState] = useState(null);
// Memoize the handler so the effect doesn't re-subscribe on every render
const handleNetInfoChange = useCallback((state) => {
setNetworkState(state);
}, []);
useFocusEffect(
useCallback(() => {
// Start subscription when the screen gains focus
const unsubscribe = NetInfo.addEventListener(handleNetInfoChange);
// Cleanup runs when the screen loses focus or unmounts
return () => {
unsubscribe();
};
}, [handleNetInfoChange])
);
return (
{networkState ? (
Connected via: {networkState.type}
) : (
Checking connection...
)}
);
}
This component can be used as a screen in a stack or tab navigator. NetInfo requires the ACCESS_NETWORK_STATE permission on Android and no extra permission on iOS.
To verify the behavior, add a console.log inside the callback and another inside the cleanup. Navigate to the screen and then away: the logs should appear in pairs, in that order. You can also add a temporary counter that increments on focus and decrements on blur to confirm the subscription count returns to zero when the screen is hidden. This example was not run against a specific version; check it against your installed @react-navigation/native version (the hook has been stable since v5).
Risks: if you forget to return the cleanup function, the NetInfo listener stays active, producing stale updates and a leak. If the callback performs asynchronous work such as a fetch, cancel it or guard with an isActive flag in the cleanup so a late response does not update an unfocused screen.
Trade-off and limitation
useFocusEffect re-executes on every focus change, including when a transparent modal or overlay appears over the screen. For expensive operations such as large data fetches or heavy animations, this can cause UI jank. Mitigate by wrapping logic in useCallback with correct dependencies, and debounce or throttle work inside the callback.
Actionable closing
Adopt useFocusEffect for any side effect that should only run while a screen is visible:
- Always return a cleanup function or nothing.
- Capture stable references with
useCallbackoruseRef. - Debounce or throttle expensive work to avoid needless re-runs on modal overlays.
Following these patterns keeps your app responsive and prevents resource leaks from hidden screens.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.