← Back to list

Mastering React Navigation Hooks

React Navigation has been the standard navigation solution for React Native apps for years. As applications grow, passing navigation props…

Onix React · 2026-06-15 11:36 · 1 claps · 7.2 min read paywalled
#react #react-native #technology #mobile-app-development #javascript
Open on Medium ↗
Wiki topics: 🌐 · Web Development 📱 · Mobile Development

Mastering React Navigation Hooks

React Navigation has been the standard navigation solution for React Native apps for years. As applications grow, passing navigation props through multiple components or managing navigation logic manually becomes harder to maintain.

Hooks introduced in React Navigation v5 provide a cleaner approach. They allow any component to access exactly what it needs from the navigation system — without extra wrappers, prop drilling, or unnecessary complexity.

With hooks, you can easily work with navigation state, themes, deep links, screen lifecycle events, and custom navigation behavior while keeping components simple and reusable.

In this article, we’ll explore some of the most useful hooks, understand when to use them, how they work internally, and the small details that matter when building production React Native applications

Theming & Appearance 🖼

useTheme

Returns the current navigation theme object, which includes a colors map and a dark boolean. The theme is set on the navigator via the theme prop and automatically propagates to all nested components through React context.

  • 👨‍💻 When to use: Any component that needs to match the navigator’s color scheme — buttons, cards, headers, icons — without receiving colors as props or setting up its own context.
  • ⚒️ How it works: Reads from ThemeContext under the hood. When you change the theme on the navigator, all consuming components re-render automatically.
import { useTheme } from '@react-navigation/native';

function MyButton() {
  const { colors, dark } = useTheme();
  return (
    <TouchableOpacity style={{ backgroundColor: colors.card }}>
      <Text style={{ color: colors.text }}>
        {dark ? 'Dark mode on' : 'Light mode on'}
      </Text>
    </TouchableOpacity>
  );
}

Class component — use ThemeContext directly

import { ThemeContext } from '@react-navigation/native';

class MyButton extends React.Component {
  static contextType = ThemeContext;

  render() {
    const { colors } = this.context;
    return (
      <TouchableOpacity style={{ backgroundColor: colors.card }}>
        <Text style={{ color: colors.text }}>Button</Text>
      </TouchableOpacity>
    );
  }
}

⚠️ Nuance: Available color keys: primary, background, card, text, border, notification. You can extend the theme by merging your own keys into the theme object — they'll be available on colors throughout the tree.

Navigation & linking 🗺

useLinkTo

Returns a linkTo(path) function that navigates imperatively using a URL-style path string. Internally it resolves the path against your linking configuration and dispatches the correct navigation action.

  • 👨‍💻 When to use: When the target screen comes from a string at runtime — a push notification payload, a server response, a QR code scan — rather than a hard-coded screen name.
  • ⚒️ How it works: Parses the path using the same linking config as Linking.openURL, then calls navigation.navigate with the resolved screen and params.
import { useLinkTo } from '@react-navigation/native';

function NotificationHandler({ path }) {
  const linkTo = useLinkTo();

  React.useEffect(() => {
    if (path) linkTo(path); // e.g. '/orders/42/details'
  }, [path]);

  return null;
}

Class component — wrap in a HOC

import { useLinkTo } from '@react-navigation/native';

function withLinkTo(Component) {
  return function Wrapper(props) {
    const linkTo = useLinkTo();
    return <Component {...props} linkTo={linkTo} />;
  };
}

class MyScreen extends React.Component {
  handlePress() {
    this.props.linkTo('/profile/jane');
  }
}
export default withLinkTo(MyScreen);

⚠️ Watch out: The path must be configured in your linking setup — if a screen has no matching path, linkTo will throw. Always validate incoming paths before passing them to this hook in production.

useLinkProps

Returns an object of props (href, role, onPress, accessibilityRole) that you spread onto any pressable element. On web the element becomes a real <a> tag, enabling right-click menus, Ctrl/⌘+Click to open in a new tab, and correct screen-reader announcements.

  • 👨‍💻 When to use: When you need a navigation element to behave like a real hyperlink on web — not just a pressable that calls navigate(). Especially important in universal (web + native) apps.
  • ⚒️ How it works: On native it builds an onPress that dispatches the navigation action. On web it adds an href attribute so the browser treats the element as a link.
import { useLinkProps } from '@react-navigation/native';

const LinkButton = ({ screen, params, children, ...rest }) => {
  const props = useLinkProps({ screen, params });
  return (
    <Pressable {...props} {...rest}>
      <Text>{children}</Text>
    </Pressable>
  );
};

Class component — HOC pattern

function withLinkProps(Component) {
  return function Wrapper({ screen, params, ...rest }) {
    const linkProps = useLinkProps({ screen, params });
    return <Component {...rest} linkProps={linkProps} />;
  };
}

💡 Tip: You can also pass an action or explicit href prop to override the generated URL — useful when your deep link path differs from what the navigator would derive automatically.

useLinkBuilder

Returns buildHref(name, params) and buildAction(name, params). buildHref constructs a URL path for any screen in the current navigator; buildAction returns the navigation action object. Designed for building reusable custom navigators that work across multiple apps.

  • 👨‍💻 When to use: Inside custom drawer content, tab bars, or navigators — when you need to generate href attributes for links programmatically based on the current linking config.
  • ⚒️ How it works: Reads the linking configuration from context and runs the screen name + params through the path-building logic, returning the same path that Linking.openURL would expect.
import { useLinkBuilder } from '@react-navigation/native';
import { PlatformPressable } from '@react-navigation/elements';

function DrawerContent({ state, descriptors, navigation }) {
  const { buildHref } = useLinkBuilder();

  return state.routes.map((route) => (
    <PlatformPressable
      key={route.key}
      href={buildHref(route.name, route.params)}
      onPress={() => navigation.navigate(route.name)}
    >
      {descriptors[route.key].options.title}
    </PlatformPressable>
  ));
}

⚠️ Important constraint: The destination screen must exist in the current navigator — not a parent or a nested child. This hook is intentionally scoped to keep custom navigators self-contained and portable across apps. For regular app code, use screen names directly.

Screen lifecycle 💻

useFocusEffect

Runs a side-effect every time the screen gains focus, and cleans it up every time the screen loses focus. Unlike useEffect, this fires not just on mount but on every navigation-in — making it ideal for data that goes stale while the user is on another screen.

  • When to use: Refreshing a list after the user creates an item on a detail screen, resuming a timer or media player, re-subscribing to a real-time feed each time the screen becomes active.
  • How it works: Subscribes to the navigator’s focus and blur events. The callback fires on focus; the returned cleanup function fires on blur. Both fire on every focus cycle, not just mount/unmount.
import { useFocusEffect } from '@react-navigation/native';

function OrdersScreen() {
  const [orders, setOrders] = React.useState([]);

  useFocusEffect(
    React.useCallback(() => {
      let active = true;
      fetchOrders().then((data) => {
        if (active) setOrders(data);
      });
      return () => { active = false; }; // cancel on blur
    }, [])
  );
}
  • ‼️ Common mistake: Always wrap your callback in React.useCallback. Without it a new function reference is created on every render, causing the effect to re-run constantly — not just on focus.
  • ⚠️ Nuance: If you only need to run something on mount + unmount and don’t care about focus cycles, stick with useEffect. useFocusEffect is specifically for the “runs again when you come back” pattern.

Route & state inspection 🚀

useRoutePath

Returns the deep link path string that maps to the currently active route. The path is derived from your linking configuration and reflects any route params serialized into the URL.

  • 👨‍💻 When to use: Building a “share this page” feature, generating canonical URLs for SEO in universal apps, or logging/analytics that tracks users by their current deep link path.
  • ⚒️ How it works: Reads the current route from the navigation state and runs it through the same path-serialization logic that getPathFromState uses internally.
import { useRoutePath } from '@react-navigation/native';

function ShareButton() {
  const path = useRoutePath();
  const url = new URL(path, 'https://myapp.com').href;

  return (
    <Button onPress={() => Share.share({ message: url })}>
      Share
    </Button>
  );
}

Class component — use getPathFromState manually

import { getPathFromState, useNavigation } from '@react-navigation/native';

// No direct equivalent — build a functional wrapper or use NavigationContext
function withRoutePath(Component) {
  return function Wrapper(props) {
    const path = useRoutePath();
    return <Component {...props} routePath={path} />;
  };
}

⚠️ Nuance: The returned path is only as accurate as your linking configuration. Screens without a configured path will return an empty string or a fallback. Always define paths for screens you intend to share.

useNavigationState

Accepts a selector function and returns only the slice of the navigation state you care about. The component re-renders only when that selected value changes — so you get precise, efficient reads from the navigation state without subscribing to the entire state tree.

  • 👨‍💻 When to use: Reading the name of the previous route, knowing whether the current screen is the root of its stack, conditionally showing a back button, or any logic that depends on the navigation stack shape.
  • ⚒️ How it works: Subscribes to the full navigation state but runs the selector on every change, and only triggers a re-render if the selector’s return value has changed (reference equality check).
import { useNavigationState } from '@react-navigation/native';

function useIsFirstRouteInParent() {
  const route = useRoute();
  const isFirstRouteInParent = useNavigationState(
    (state) => state.routes[0].key === route.key
  );

  return isFirstRouteInParent;
}

function usePreviousRouteName() {
  return useNavigationState((state) =>
    state.routes[state.index - 1]?.name
      ? state.routes[state.index - 1].name
      : 'None'
  );
}

💡 Performance tip: Keep selectors cheap and stable. If you return a new object on every call (e.g. state => ({ index: state.index })), the equality check always fails and the component re-renders on every navigation event. Return primitives or memoize complex selections.

Scroll behavior 📈

useScrollToTop

Connects a scrollable component to the tab navigator so that tapping the active tab scrolls the list back to the top — the same behavior users expect from native iOS and Android tab bars. Pass a ref to any ScrollView, FlatList, or SectionList.

  • When to use: Any tab screen with a scrollable content list. It’s a one-liner that delivers an important piece of native platform polish your users will notice if it’s missing.
  • How it works: Listens for a tab press event when the screen is already focused, then calls ref.current.scrollToOffset({ offset: 0 }) (or the equivalent for ScrollView) automatically.
import { ScrollView } from 'react-native';
import { useScrollToTop } from '@react-navigation/native';

function FeedScreen() {
  const ref = React.useRef(null);
  useScrollToTop(ref);

  return (
    <FlatList
      ref={ref}
      data={items}
      renderItem={({ item }) => <Item data={item} />}
    />
  );
}
  • ⚠️ Nuance: Works with ScrollView, FlatList, and SectionList out of the box. For custom scrollable components, your component needs to expose a scrollToOffset or scrollTo method on its ref for this hook to be able to call it.
  • 💡 Tip: Only fires when the tab is already focused and the user taps it again. First tap navigates to the tab as usual; second tap scrolls to top. This matches the exact native behavior on both platforms.

Conclusion

React Navigation hooks provide a simpler and more flexible way to work with navigation in React Native applications.

They help reduce boilerplate, keep components independent, and make it easier to handle common tasks like accessing themes, working with deep links, reacting to screen changes, and reading navigation state.

By using the right hook for the right scenario, you can build cleaner, more maintainable, and more native-feeling navigation experiences.

**Telegram / Instagram / Threads / X / YouTube / GitHub**


메타데이터
post_id
df24e6941259
slug
mastering-react-navigation-hooks-df24e6941259
url
https://medium.com/@onix_react/mastering-react-navigation-hooks-df24e6941259
canonical_url
https://medium.com/@onix_react/mastering-react-navigation-hooks-df24e6941259
author_url
https://medium.com/@onix_react
status
ok
fetched_at
2026-06-16 19:09:56