A cross-platform Tab View component for React Native.
This is a JavaScript-only implementation of swipeable tab views. It's super customizable, allowing you to do things like coverflow.
- Run the example app to see it in action.
- Checkout the example/ folder for source code.
Requires React Native version >= 0.36.
- Smooth animations and gestures
- Scrollable tabs
- Both top and bottom tab bars
- Follows Material Design spec
- Highly customizable
npm install --save react-native-tab-view
import React, { Component } from 'react';
import { View, StyleSheet } from 'react-native';
import { TabViewAnimated, TabBar } from 'react-native-tab-view';
const styles = StyleSheet.create({
container: {
flex: 1,
},
page: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
});
export default class TabViewExample extends Component {
state = {
index: 0,
routes: [
{ key: '1', title: 'First' },
{ key: '2', title: 'Second' },
],
};
_handleChangeTab = (index) => {
this.setState({ index });
};
_renderHeader = (props) => {
return <TabBar {...props} />;
};
_renderScene = ({ route }) => {
switch (route.key) {
case '1':
return <View style={[ styles.page, { backgroundColor: '#ff4081' } ]} />;
case '2':
return <View style={[ styles.page, { backgroundColor: '#673ab7' } ]} />;
default:
return null;
}
};
render() {
return (
<TabViewAnimated
style={styles.container}
navigationState={this.state}
renderScene={this._renderScene}
renderHeader={this._renderHeader}
onRequestChangeTab={this._handleChangeTab}
/>
);
}
}
The package exposes the following components,
Container component responsible for managing tab transitions
It accepts the following props,
navigationState
- the current navigation stateconfigureTransition
- optional callback which returns a configuration for the transition, returnnull
to disable animationonRequestChangeTab
- callback for when the current tab changes, should do thesetState
onChangePosition
- callback called with position value as it changes (e.g. - on swipe or tab change), avoid doing anything expensive herecanJumpToTab
- optional callback which accepts a route, and returns a boolean indicating whether jumping to the tab is allowedlazy
- whether to load tabs lazily when you start switchinginitialLayout
- optional object containing the initialheight
andwidth
, can be passed to prevent the one frame delay in renderingrenderPager
- optional callback which renders a pager responsible for handling swipesrenderHeader
- optional callback which renders a header, useful for a top tab barrenderFooter
- optional callback which renders a footer, useful for a bottom tab barrenderScene
- callback which renders a single scene
Pager component based on PanResponder
It accepts the following props,
swipeEnabled
- whether to enable swipe gesturesswipeDistanceThreshold
- minimum swipe distance to trigger page switchswipeVelocityThreshold
- minimum swipe velocity to trigger page switchchildren
- React Element(s) to render
Pager component based on ScrollView
(default on iOS)
It accepts the following props,
swipeEnabled
- whether to enable swipe gestureschildren
- React Element(s) to render
Pager component based on ViewPagerAndroid
(default on Android)
It accepts the following props,
swipeEnabled
- whether to enable swipe gesturesanimationEnabled
- whether to enable page change animationchildren
- React Element(s) to render
Material design themed top tab bar
It accepts the following props,
getLabelText
- optional callback which receives the current scene and returns the tab labelrenderIcon
- optional callback which receives the current scene and returns a React Element to be used as a iconrenderLabel
- optional callback which receives the current scene and returns a React Element to be used as a labelrenderIndicator
- optional callback which receives the current scene and returns a React Element to be used as a tab indicatorrenderBadge
- optional callback which receives the current scene and returns a React Element to be used as a badgeonTabPress
- optional callback invoked on tab press, useful for things like scroll to toppressColor
- color for material ripple (Android >= 5.0 only)pressOpacity
- opacity for pressed tab (iOS and Android < 5.0 only)scrollEnabled
- whether to enable scrollable tabstabWidth
- optional custom tab widthtabStyle
- style object for the tabindicatorStyle
- style object for the tab indicatorlabelStyle
- style object for the tab label
Check the type definitions for details on shape of different objects.
<TabViewAnimated />
is a PureComponent
to prevent unnecessary re-rendering. As a side-effect, the tabs won't re-render if something changes in the parent's state. If you need it to trigger a re-render, put it in the navigationState
.
For example, consider you have a loaded
property on state which should trigger re-render. You can have your state like the following -
state = {
index: 0,
routes: [
{ key: '1', title: 'First' },
{ key: '2', title: 'Second' },
],
loaded: false,
}
Then pass this.state
as the navigationState
prop to <TabViewAnimated />
or <TabViewTransitioner />
.
<TabViewAnimated
navigationState={this.state}
renderScene={this._renderPage}
renderHeader={this._renderHeader}
onRequestChangeTab={this._handleChangeTab}
/>
The renderScene
function is called every time the index changes. If your renderScene
function is expensive, it's good idea move each route to a separate component if they don't depend on the index, and apply shouldComponentUpdate
to prevent unnecessary re-renders.
For example, instead of:
renderScene = ({ route }) => {
switch (route.key) {
case 'home':
return (
<View style={styles.page}>
<Avatar />
<NewsFeed />>
</View>
);
default:
return null;
}
}
Do the following:
renderScene = ({ route }) => {
switch (route.key) {
case 'home':
return <HomeComponent />;
default:
return null;
}
}
Where <HomeComponent />
is a PureComponent
.
We need to measure the width of the container and hence need to wait before rendering. This is especially visible when you are rendering more than one tab on screen, such as coverflow. If you know the initial width upfront, you can pass it in and we won't need to wait for measuring it. Most of the time, it's just the window width.
For example, pass the following initialLayout
to TabViewAnimated
:
const initialLayout = {
height: 0,
width: Dimensions.get('window').width,
};
The tabview will still react to changes in the dimension and adjust accordingly to accommodate things like orientation change.
If you've a large number of routes, especially images, it can slow the animation down a lot. You can instead render a limited number of routes.
For example, do the following to render only 2 routes on each side:
renderScene = ({ route }) => {
if (Math.abs(this.state.index - this.state.routes.indexOf(route)) > 2) {
return null;
}
return <MySceneComponent route={route} />;
};
zIndex
in React Native is buggy which results in weird behaviour when you remove adjacent items from the hierarchy. You can try setting zIndex
to 0
for the TabBar
to avoid the issue.