# How to Programmatically Navigate Between Tabs in React Native Tab View

> Easily programmatically navigate between tabs in React Native. Learn to control tab changes by updating navigationState or using onIndexChange for smooth user experiences.

- Repository: [Meta/react](https://github.com/facebook/react)
- Tags: how-to-guide
- Published: 2026-02-19

---

**TLDR:** Update the `index` property within the `navigationState` prop passed to the `TabView` component to programmatically switch tabs, or invoke the `onIndexChange` callback with the target index to trigger a navigation change.

The `react-native-tab-view` library implements a **controlled component** pattern for React Native tab interfaces, meaning the active tab is determined by external state rather than internal component state. This architecture, defined in [`src/TabView.tsx`](https://github.com/facebook/react/blob/main/src/TabView.tsx) and [`src/types.ts`](https://github.com/facebook/react/blob/main/src/types.ts), requires you to manage the `navigationState` object—containing the `index` and `routes` arrays—giving you explicit control over programmatic navigation.

## Understanding the Controlled Component Architecture

In [`src/TabView.tsx`](https://github.com/facebook/react/blob/main/src/TabView.tsx), the component accepts a `navigationState` prop that follows the `NavigationState` type defined in [`src/types.ts`](https://github.com/facebook/react/blob/main/src/types.ts). This object structure determines which tab is currently visible:

```typescript
navigationState={{
  index: 0,        // Zero-based position of active tab
  routes: [        // Array of Route objects
    { key: 'first', title: 'First Tab' },
    { key: 'second', title: 'Second Tab' }
  ]
}}

```

The `TabView` renders content through the `renderScene` callback and reports user interactions via `onIndexChange`. When a user swipes between tabs, the gesture handler in [`src/TabViewPagerPan.tsx`](https://github.com/facebook/react/blob/main/src/TabViewPagerPan.tsx) calculates the new index and invokes `onIndexChange`, allowing you to update your state. Conversely, updating the `index` in your state and passing it back to `navigationState` forces the component to animate to that tab programmatically.

## Core Methods for Programmatic Navigation

### Updating State Directly via onIndexChange

The simplest approach maintains the tab index in local state and updates it programmatically:

```jsx
import React, { useState, useCallback } from 'react';
import { TabView, SceneMap } from 'react-native-tab-view';

const FirstRoute = () => <View style={{ flex: 1, backgroundColor: '#ff4081' }} />;
const SecondRoute = () => <View style={{ flex: 1, backgroundColor: '#673ab7' }} />;

export default function TabNavigator() {
  const [index, setIndex] = useState(0);
  const [routes] = useState([
    { key: 'first', title: 'First' },
    { key: 'second', title: 'Second' },
  ]);

  // Programmatic navigation function
  const jumpToTab = useCallback((targetIndex) => {
    if (targetIndex >= 0 && targetIndex < routes.length) {
      setIndex(targetIndex);
    }
  }, [routes.length]);

  return (
    <>
      <TabView
        navigationState={{ index, routes }}
        renderScene={SceneMap({
          first: FirstRoute,
          second: SecondRoute,
        })}
        onIndexChange={setIndex}
      />
      <Button title="Go to Second Tab" onPress={() => jumpToTab(1)} />
    </>
  );
}

```

### Navigating by Route Key Instead of Index

Hardcoding numeric indices creates brittle code if tab order changes. Instead, navigate by **route key** by mapping the key to its current index:

```jsx
const jumpTo = useCallback((key) => {
  const routeIndex = routes.findIndex(route => route.key === key);
  if (routeIndex !== -1) {
    setIndex(routeIndex);
  }
}, [routes]);

// Usage
jumpTo('settings');  // Navigates to the tab with key 'settings'

```

This approach is more maintainable when tabs are dynamically added, removed, or reordered.

### Using Refs with useImperativeHandle

For scenarios where you need to trigger navigation from parent components or deep callbacks where state setters aren't accessible, expose navigation methods via a `ref` using `useImperativeHandle`:

```jsx
import React, { useState, useRef, forwardRef, useImperativeHandle } from 'react';
import { TabView, SceneMap } from 'react-native-tab-view';

const TabViewWrapper = forwardRef((props, ref) => {
  const [index, setIndex] = useState(0);
  const [routes] = useState([
    { key: 'home', title: 'Home' },
    { key: 'profile', title: 'Profile' },
  ]);

  useImperativeHandle(ref, () => ({
    navigateToKey: (key) => {
      const targetIndex = routes.findIndex(r => r.key === key);
      if (targetIndex !== -1) setIndex(targetIndex);
    },
    navigateToIndex: (idx) => {
      if (idx >= 0 && idx < routes.length) setIndex(idx);
    }
  }));

  return (
    <TabView
      navigationState={{ index, routes }}
      renderScene={SceneMap({
        home: () => <Text>Home Content</Text>,
        profile: () => <Text>Profile Content</Text>,
      })}
      onIndexChange={setIndex}
    />
  );
});

// Parent component usage
export default function Parent() {
  const tabRef = useRef(null);
  
  return (
    <>
      <TabViewWrapper ref={tabRef} />
      <Button 
        title="Jump to Profile" 
        onPress={() => tabRef.current?.navigateToKey('profile')} 
      />
    </>
  );
}

```

### React Context for Deep Component Trees

When navigation triggers originate from deeply nested components, avoid prop drilling by providing navigation functions through React Context:

```jsx
// TabNavigationContext.js
import { createContext, useContext, useState, useCallback } from 'react';

const TabContext = createContext(null);
export const useTabNavigation = () => useContext(TabContext);

export const TabProvider = ({ children, initialRoutes }) => {
  const [index, setIndex] = useState(0);
  const [routes] = useState(initialRoutes);

  const jumpTo = useCallback((key) => {
    const idx = routes.findIndex(r => r.key === key);
    if (idx !== -1) setIndex(idx);
  }, [routes]);

  return (
    <TabContext.Provider value={{ index, routes, setIndex, jumpTo }}>
      {children}
    </TabContext.Provider>
  );
};

```

Any child component can now import `useTabNavigation` and call `jumpTo('targetKey')` without receiving props through intermediate components.

## Key Implementation Files

According to the `react-native-tab-view` source code, these files define the core navigation behavior:

- **[`src/TabView.tsx`](https://github.com/facebook/react/blob/main/src/TabView.tsx)**: Implements the controlled component logic, accepting `navigationState` and `onIndexChange` props to coordinate tab displays.
- **[`src/TabViewPagerPan.tsx`](https://github.com/facebook/react/blob/main/src/TabViewPagerPan.tsx)**: Contains the gesture handling logic that translates swipe gestures into `onIndexChange` calls with calculated index values.
- **[`src/types.ts`](https://github.com/facebook/react/blob/main/src/types.ts)**: Defines the TypeScript interfaces for `NavigationState`, `Route`, and callback signatures including `onIndexChange` and `renderScene`.

## Summary

- **Controlled Pattern**: The `TabView` component in [`src/TabView.tsx`](https://github.com/facebook/react/blob/main/src/TabView.tsx) is fully controlled via the `navigationState.index` prop—updating this value programmatically switches tabs.
- **State Management**: Use `useState` to track the current index and pass the setter to `onIndexChange` for both user gestures and programmatic navigation.
- **Key-Based Navigation**: Map route keys to indices using `routes.findIndex()` to create stable navigation logic that survives tab reordering.
- **Ref Exposure**: Use `useImperativeHandle` to expose `jumpTo` or `setIndex` methods on component refs when direct state access is impractical.
- **Context Integration**: Implement a Context Provider to distribute navigation functions throughout deep component trees without prop drilling.

## Frequently Asked Questions

### Can I call a jumpTo method directly on the TabView ref?

The `react-native-tab-view` library does not natively expose a `jumpTo` method on component refs. However, you can implement this functionality by wrapping `TabView` in a custom component that uses `useImperativeHandle` to expose navigation methods, as shown in the ref-based examples above.

### How do I disable swipe gestures while keeping programmatic navigation?

Set the `swipeEnabled` prop to `false` on the `TabView` component. This disables the gesture handler in [`src/TabViewPagerPan.tsx`](https://github.com/facebook/react/blob/main/src/TabViewPagerPan.tsx) while preserving the ability to navigate programmatically by updating the `index` in `navigationState`.

### What is the difference between index-based and key-based navigation?

**Index-based navigation** uses numeric positions (0, 1, 2) to identify tabs, which is simple but fragile if tab order changes. **Key-based navigation** uses string identifiers defined in the `key` property of each route object, making your navigation logic resilient to changes in tab ordering or dynamic tab addition/removal.

### How does programmatic navigation integrate with React Navigation?

When embedding a `TabView` inside a React Navigation screen, maintain the tab index in the screen's component state or in a global state manager. You can synchronize the tab index with React Navigation's navigation state using `useFocusEffect` or by dispatching navigation actions that update the parent route parameters, though the core mechanism remains updating the `navigationState.index` prop.