GithubHelp home page GithubHelp logo

satya164 / react-native-tab-view Goto Github PK

View Code? Open in Web Editor NEW
5.1K 62.0 1.1K 9.51 MB

A cross-platform Tab View component for React Native

License: MIT License

JavaScript 2.71% TypeScript 97.29%
react-native pager-component swipeview swipe tabs tabbar hacktoberfest

react-native-tab-view's Introduction

The repo has been moved to https://github.com/react-navigation/react-navigation/tree/main/packages/react-native-tab-view. Please open issues and pull requests there.

React Native Tab View

Build Status Version MIT License

A cross-platform Tab View component for React Native. Implemented using react-native-pager-view on Android & iOS, and PanResponder on Web, macOS, and Windows.

Features

  • Smooth animations and gestures
  • Scrollable tabs
  • Supports both top and bottom tab bars
  • Follows Material Design spec
  • Highly customizable
  • Fully typed with TypeScript

Demo

React Native Compatibility

To use this library you need to ensure you are using the correct version of React Native. If you are using a version of React Native that is lower than 0.63 you will need to upgrade that before attempting to use this library.

react-native-tab-view Version Required React Native Version
2.x.x < 0.63
3.x.x >= 0.63

Installation

Open a Terminal in the project root and run:

yarn add react-native-tab-view

Now we need to install react-native-pager-view if you plan to support iOS and Android.

If you are using Expo, to ensure that you get the compatible versions of the libraries, run:

expo install react-native-pager-view

If you are not using Expo, run the following:

yarn add react-native-pager-view

We're done! Now you can build and run the app on your device/simulator.

Quick Start

import * as React from 'react';
import { View, useWindowDimensions } from 'react-native';
import { TabView, SceneMap } from 'react-native-tab-view';

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

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

const renderScene = SceneMap({
  first: FirstRoute,
  second: SecondRoute,
});

export default function TabViewExample() {
  const layout = useWindowDimensions();

  const [index, setIndex] = React.useState(0);
  const [routes] = React.useState([
    { key: 'first', title: 'First' },
    { key: 'second', title: 'Second' },
  ]);

  return (
    <TabView
      navigationState={{ index, routes }}
      renderScene={renderScene}
      onIndexChange={setIndex}
      initialLayout={{ width: layout.width }}
    />
  );
}

Try this example on Snack

More examples on Snack

API reference

The package exports a TabView component which is the one you'd use to render the tab view, and a TabBar component which is the default tab bar implementation.

TabView

Container component responsible for rendering and managing tabs. Follows material design styles by default.

Basic usage look like this:

<TabView
  navigationState={{ index, routes }}
  onIndexChange={setIndex}
  renderScene={SceneMap({
    first: FirstRoute,
    second: SecondRoute,
  })}
/>

TabView Props

navigationState (required)

State for the tab view. The state should contain the following properties:

  • index: a number representing the index of the active route in the routes array
  • routes: an array containing a list of route objects used for rendering the tabs

Each route object should contain the following properties:

  • key: a unique key to identify the route (required)
  • title: title for the route to display in the tab bar
  • icon: icon for the route to display in the tab bar
  • accessibilityLabel: accessibility label for the tab button
  • testID: test id for the tab button

Example:

{
  index: 1,
  routes: [
    { key: 'music', title: 'Music' },
    { key: 'albums', title: 'Albums' },
    { key: 'recents', title: 'Recents' },
    { key: 'purchased', title: 'Purchased' },
  ]
}

TabView is a controlled component, which means the index needs to be updated via the onIndexChange callback.

onIndexChange (required)

Callback which is called on tab change, receives the index of the new tab as argument. The navigation state needs to be updated when it's called, otherwise the change is dropped.

renderScene (required)

Callback which returns a react element to render as the page for the tab. Receives an object containing the route as the argument:

const renderScene = ({ route, jumpTo }) => {
  switch (route.key) {
    case 'music':
      return <MusicRoute jumpTo={jumpTo} />;
    case 'albums':
      return <AlbumsRoute jumpTo={jumpTo} />;
  }
};

You need to make sure that your individual routes implement a shouldComponentUpdate to improve the performance. To make it easier to specify the components, you can use the SceneMap helper.

SceneMap takes an object with the mapping of route.key to React components and returns a function to use with renderScene prop.

import { SceneMap } from 'react-native-tab-view';

...

const renderScene = SceneMap({
  music: MusicRoute,
  albums: AlbumsRoute,
});

Specifying the components this way is easier and takes care of implementing a shouldComponentUpdate method.

Each scene receives the following props:

  • route: the current route rendered by the component
  • jumpTo: method to jump to other tabs, takes a route.key as it's argument
  • position: animated node which represents the current position

The jumpTo method can be used to navigate to other tabs programmatically:

this.props.jumpTo('albums');

All the scenes rendered with SceneMap are optimized using React.PureComponent and don't re-render when parent's props or states change. If you need more control over how your scenes update (e.g. - triggering a re-render even if the navigationState didn't change), use renderScene directly instead of using SceneMap.

IMPORTANT: Do not pass inline functions to SceneMap, for example, don't do the following:

SceneMap({
  first: () => <FirstRoute foo={this.props.foo} />,
  second: SecondRoute,
});

Always define your components elsewhere in the top level of the file. If you pass inline functions, it'll re-create the component every render, which will cause the entire route to unmount and remount every change. It's very bad for performance and will also cause any local state to be lost.

If you need to pass additional props, use a custom renderScene function:

const renderScene = ({ route }) => {
  switch (route.key) {
    case 'first':
      return <FirstRoute foo={this.props.foo} />;
    case 'second':
      return <SecondRoute />;
    default:
      return null;
  }
};
renderTabBar

Callback which returns a custom React Element to use as the tab bar:

import { TabBar } from 'react-native-tab-view';

...

<TabView
  renderTabBar={props => <TabBar {...props} />}
  ...
/>

If this is not specified, the default tab bar is rendered. You pass this props to customize the default tab bar, provide your own tab bar, or disable the tab bar completely.

<TabView
  renderTabBar={() => null}
  ...
/>
tabBarPosition

Position of the tab bar in the tab view. Possible values are 'top' and 'bottom'. Defaults to 'top'.

lazy

Function which takes an object with the current route and returns a boolean to indicate whether to lazily render the scenes.

By default all scenes are rendered to provide a smoother swipe experience. But you might want to defer the rendering of unfocused scenes until the user sees them. To enable lazy rendering for a particular scene, return true from getLazy for that route:

<TabView
  lazy={({ route }) => route.name === 'Albums'}
  ...
/>

When you enable lazy rendering for a screen, it will usually take some time to render when it comes into focus. You can use the renderLazyPlaceholder prop to customize what the user sees during this short period.

You can also pass a boolean to enable lazy for all of the scenes:

<TabView
  lazy
/>
lazyPreloadDistance

When lazy is enabled, you can specify how many adjacent routes should be preloaded with this prop. This value defaults to 0 which means lazy pages are loaded as they come into the viewport.

renderLazyPlaceholder

Callback which returns a custom React Element to render for routes that haven't been rendered yet. Receives an object containing the route as the argument. The lazy prop also needs to be enabled.

This view is usually only shown for a split second. Keep it lightweight.

By default, this renders null.

keyboardDismissMode

String indicating whether the keyboard gets dismissed in response to a drag gesture. Possible values are:

  • 'auto' (default): the keyboard is dismissed when the index changes.
  • 'on-drag': the keyboard is dismissed when a drag begins.
  • 'none': drags do not dismiss the keyboard.
swipeEnabled

Boolean indicating whether to enable swipe gestures. Swipe gestures are enabled by default. Passing false will disable swipe gestures, but the user can still switch tabs by pressing the tab bar.

animationEnabled

Enables animation when changing tab. By default it's true.

onSwipeStart

Callback which is called when the swipe gesture starts, i.e. the user touches the screen and moves it.

onSwipeEnd

Callback which is called when the swipe gesture ends, i.e. the user lifts their finger from the screen after the swipe gesture.

initialLayout

Object containing the initial height and width of the screens. Passing this will improve the initial rendering performance. For most apps, this is a good default:

<TabView
  initialLayout={{ width: Dimensions.get('window').width }}
  ...
/>
sceneContainerStyle

Style to apply to the view wrapping each screen. You can pass this to override some default styles such as overflow clipping:

pagerStyle

Style to apply to the pager view wrapping all the scenes.

style

Style to apply to the tab view container.

TabBar

Material design themed tab bar. To customize the tab bar, you'd need to use the renderTabBar prop of TabView to render the TabBar and pass additional props.

For example, to customize the indicator color and the tab bar background color, you can pass indicatorStyle and style props to the TabBar respectively:

const renderTabBar = props => (
  <TabBar
    {...props}
    indicatorStyle={{ backgroundColor: 'white' }}
    style={{ backgroundColor: 'pink' }}
  />
);

//...


return (
  <TabView
    renderTabBar={renderTabBar}
    ...
  />
);

TabBar Props

getLabelText

Function which takes an object with the current route and returns the label text for the tab. Uses route.title by default.

<TabBar
  getLabelText={({ route }) => route.title}
  ...
/>
getAccessible

Function which takes an object with the current route and returns a boolean to indicate whether to mark a tab as accessible. Defaults to true.

getAccessibilityLabel

Function which takes an object with the current route and returns a accessibility label for the tab button. Uses route.accessibilityLabel by default if specified, otherwise uses the route title.

<TabBar
  getAccessibilityLabel={({ route }) => route.accessibilityLabel}
  ...
/>
getTestID

Function which takes an object with the current route and returns a test id for the tab button to locate this tab button in tests. Uses route.testID by default.

<TabBar
  getTestID={({ route }) => route.testID}
  ...
/>
renderIcon

Function which takes an object with the current route, focused status and color and returns a custom React Element to be used as a icon.

<TabBar
  renderIcon={({ route, focused, color }) => (
    <Icon
      name={focused ? 'albums' : 'albums-outlined'}
      color={color}
    />
  )}
  ...
/>
renderLabel

Function which takes an object with the current route, focused status and color and returns a custom React Element to be used as a label.

<TabBar
  renderLabel={({ route, focused, color }) => (
    <Text style={{ color, margin: 8 }}>
      {route.title}
    </Text>
  )}
  ...
/>
renderTabBarItem

Function which takes a TabBarItemProps object and returns a custom React Element to be used as a tab button.

renderIndicator

Function which takes an object with the current route and returns a custom React Element to be used as a tab indicator.

renderBadge

Function which takes an object with the current route and returns a custom React Element to be used as a badge.

onTabPress

Function to execute on tab press. It receives the scene for the pressed tab, useful for things like scroll to top.

By default, tab press also switches the tab. To prevent this behavior, you can call preventDefault:

<TabBar
  onTabPress={({ route, preventDefault }) => {
    if (route.key === 'home') {
      preventDefault();

      // Do something else
    }
  }}
  ...
/>
onTabLongPress

Function to execute on tab long press, use for things like showing a menu with more options

activeColor

Custom color for icon and label in the active tab.

inactiveColor

Custom color for icon and label in the inactive tab.

pressColor

Color for material ripple (Android >= 5.0 only).

pressOpacity

Opacity for pressed tab (iOS and Android < 5.0 only).

scrollEnabled

Boolean indicating whether to make the tab bar scrollable.

If you set scrollEnabled to true, you should also specify a width in tabStyle to improve the initial render.

bounces

Boolean indicating whether the tab bar bounces when scrolling.

tabStyle

Style to apply to the individual tab items in the tab bar.

By default, all tab items take up the same pre-calculated width based on the width of the container. If you want them to take their original width, you can specify width: 'auto' in tabStyle.

indicatorStyle

Style to apply to the active indicator.

indicatorContainerStyle

Style to apply to the container view for the indicator.

labelStyle

Style to apply to the tab item label.

contentContainerStyle

Style to apply to the inner container for tabs.

style

Style to apply to the tab bar container.

gap

Define a spacing between tabs.

testID

Test id for the tabBar. Can be used for scrolling the tab bar in tests

Using with other libraries

If you want to integrate the tab view with React Navigation's navigation system, e.g. want to be able to navigate to a tab using navigation.navigate etc, you can use the following official integrations:

Note that some functionalities are not available with the React Navigation 4 integration because of the limitations in React Navigation. For example, it's possible to dynamically change the rendered tabs.

Optimization Tips

Avoid unnecessary re-renders

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 use shouldComponentUpdate or React.memo in your route components to prevent unnecessary re-renders.

For example, instead of:

const renderScene = ({ route }) => {
  switch (route.key) {
    case 'home':
      return (
        <View style={styles.page}>
          <Avatar />
          <NewsFeed />
        </View>
      );
    default:
      return null;
  }
};

Do the following:

const renderScene = ({ route }) => {
  switch (route.key) {
    case 'home':
      return <HomeComponent />;
    default:
      return null;
  }
};

Where <HomeComponent /> is a PureComponent if you're using class components:

export default class HomeComponent extends React.PureComponent {
  render() {
    return (
      <View style={styles.page}>
        <Avatar />
        <NewsFeed />
      </View>
    );
  }
}

Or, wrapped in React.memo if you're using function components:

function HomeComponent() {
  return (
    <View style={styles.page}>
      <Avatar />
      <NewsFeed />
    </View>
  );
}

export default React.memo(HomeComponent);

Avoid one frame delay

We need to measure the width of the container and hence need to wait before rendering some elements on the screen. 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 TabView:

const initialLayout = {
  height: 0,
  width: Dimensions.get('window').width,
};

The tab view will still react to changes in the dimension and adjust accordingly to accommodate things like orientation change.

Optimize large number of routes

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:

const renderScene = ({ route }) => {
  if (Math.abs(index - routes.indexOf(route)) > 2) {
    return <View />;
  }

  return <MySceneComponent route={route} />;
};

Avoid rendering TabView inside ScrollView

Nesting the TabView inside a vertical ScrollView will disable the optimizations in the FlatList components rendered inside the TabView. So avoid doing it if possible.

Use lazy and renderLazyPlaceholder props to render routes as needed

The lazy option is disabled by default to provide a smoother tab switching experience, but you can enable it and provide a placeholder component for a better lazy loading experience. Enabling lazy can improve initial load performance by rendering routes only when they come into view. Refer the prop reference for more details.

Contributing

While developing, you can run the example app to test your changes.

Make sure your code passes TypeScript and ESLint. Run the following to verify:

yarn typescript
yarn lint

To fix formatting errors, run the following:

yarn lint -- --fix

Remember to add tests for your change if possible.

react-native-tab-view's People

Contributors

ashoat avatar bdtren avatar charpeni avatar dependabot[bot] avatar fson avatar grabbou avatar imgbot[bot] avatar isnifer avatar julienvincent avatar kimak avatar knowbody avatar lebedev avatar matihabbas avatar milesj avatar mlecoq avatar mleonardallen avatar mujavidb avatar naturalclar avatar niryo avatar okwasniewski avatar osdnk avatar palisand avatar raarts avatar ricmatsui avatar ryankask avatar satya164 avatar szymonrybczak avatar volfpe avatar wli avatar zamotany avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

react-native-tab-view's Issues

How to do optimization with renderScene?

Hello,

In the optimization tips, there's the instruction to move the renderScene to a separate component and apply shouldComponentUpdate to prevent unnecessary re-renders. I have tried with different codes but they still not working. The UI is not updated as the function shouldComponentUpdate returns false.

My code is as below:

class TabViewExample extends React.Component {
  constructor(props, context) {
    super(props, context);
    
    this.state = {
      index: 0,
      routes: [
        { key: 'FooScreen', title: 'Foo'},
        { key: 'BarScreen', title: 'Bar'},
      ],      
    };
  }

  _handleChangeTab = (index) => {
    this.setState({
      index,
    });
  };
  
  render() {
    return (
      <View style={styles.container}>
        <MySeparateComponent
          style={{ flex: 1 }}
          navigationState={this.state}
          onRequestChangeTab={this._handleChangeTab}
        />        
      </View>
    );
  }
}

//////////////////////////////////

class MySeparateComponent extends React.Component {
  constructor(props, context) {
    super(props, context);
  }

  shouldComponentUpdate(nextProps, nextState) {
    return false;
  }
  
  _renderScene = ({route}) => {
    let ComponentClass = RouteHelper.resolveComponentClass(route.key);
    return <ComponentClass/>;
  };

  render() {
    return (
      <View style={styles.container}>
        <TabViewAnimated
          style={{ flex: 1 }}
          navigationState={this.props.navigationState}
          renderScene={this._renderScene}
          onRequestChangeTab={this.props.onRequestChangeTab}
        />        
      </View>
    );
  }
}

I am not sure if this code correctly follows the optimization instruction. Please help!

Thanks

How To: Horizontal swipes inside TabViewAnimated

Thank you for this component <3

Trying to solve one problem my guess it's very common without any luck. App has 3 screens, on two of them I'd like to be able to swipe horizontally without changing the screen. On isolation work,but when mixing it with TabViewAnimated both fail.

screenshot

(Colored 'squares' at the bottom are inside a horizontal ListView)

screenshot

(Each item list should reveal a button on swiping left)

Should I be looking at TabViewPagerPan ?

Current iteration https://gist.github.com/polmoneys/26cfc57287e9529e098b83e62eb2c93c

Cannot disable swipe in Tab View Animated

My component and it's props:

<TabViewAnimated
    style={styles.navBar}
    navigationState={this.state}
    renderScene={this._renderScene}
    renderHeader={this._renderHeader}
    onRequestChangeTab={this._handleChangeTab} />

This is my header bar:

_renderHeader = (props) => {
    return (
      <TabBarTop
        {...props}
        style={styles.tabBar}
        indicatorStyle={styles.indicator}
        labelStyle={styles.label}  />
    );
  }

No prop to disable swipe gestures...?

Three Item at a time from 100 items

I need to render three items once out of 100, if swipe next then render next 3item if swipe prev then again render prev 3 item
what can I do
Plz sugget me

Frame drops when switching tabs

When the app starts, i make some api calls for data to update the UI in 3 (out of 5) of the tabbed components.
Now if an attempt to switch tabs coincides with the exact moment the UI within any of the tabs is being updated, there is a very obvious drop in frames, and stuttering in the tab switching animation.
Any advice on how to eliminate these?

a setState during a tab transition will cause a jump

with current way shown in the example, it looks like I have to control the tab ~ like this:

  onRequestChangeTab={this._handleChangeTab}
...
_handleChangeTab = (index) => this.setState({ index })

the problem I see is that _handleChangeTab is always called AFTER the transition is done, but not "before" , and the problem is if one setState happen or any tree update, the render() get called again with the previous tab index which causes a jump.

I have tried 0.0.37 as well as master. Both have the problem.

Shadow iOS

Works great! Removed the other scroll tab component and replaced it with this one..

The only thing that bothers me a bit is that there's no shadow on iOS, only Android. Any way to fix this?

Tab Content goes Blank after Route is Pushed then Popped

Hi! I have used react-native-tab-view to set up a four-pane system and ran into this bug. All of my panes have "filter" buttons which push a new route onto the stack. Within the filter scene, when the user is finished, they click "Done" and it will pop that route off the stack and return to the pane being managed by react-native-tab-view.

I noticed that when a route is pushed, and then subsequently popped, the tab content of the other 3 non-active tabs (which do have content to show, and originally showed that content before pushing and popping) now show up completely blank:

img_3121

In this blank area, there is a with, as I mentioned, a lot of rows rendered. It should not be blank. I also noticed that if you simply nudge this ListView component (a small swipe up or down in that blank area), the ListView content pop back up (re-render?).

I'm not sure where the bug with this lies, but my hunch is that it's in this library, so I figured I would start here!

Any help appreciated! Thanks!

Rendering pages twice

I am trying tabview using the example code given in the readme using two components as pages. The pages are rendered twice at the beginning. Do you have any idea why and any suggestions?

2nd view rendering under first view

Added the same styling as the example shows and for some reason the views are rendering on their respective views, and leaving a gap on the other. See attached.
screen shot 2016-10-04 at 10 29 46 pm
screen shot 2016-10-04 at 10 29 43 pm

style set warning as of version 0.0.35

I just upgraded to the new release (0.0.35) and now get this warning, appears to be coming from TabBarTop.js:

YellowBox.js:69 You are setting the style { position: ... } as a prop. You should nest it in a style object. E.g. { style: { position: ... } }

screen shot 2016-10-19 at 5 05 50 pm

Any way to hide tab bar?

Is there any way to hide tab bar in child views? For example if I go to a camera view, is there anyway I can hide it there?

I have an app with three tabs A, B, C. In tab C's root view navigates to a camera view which is where I want to hide the tab bar.

cannot swipe in ios simulator

i think the default example should be good enough to replicate. I haven't tried tho. Our app is using the having the same setup as default example.

cc @satya164

Local state management works fine, analog redux state management don't.

If I use a local state like in this example everything works like a charm.

But, when porting it to redux, swiping doesn't work.

Action

import { CHANGE_TAB } from './constants';

export const changeTab = (index) => ({
  type: CHANGE_TAB,
  payload: {
    index,
  },
});

Reducer

import { handleActions } from 'redux-actions';
import { CHANGE_TAB } from './constants';

const initialState = {
  navigation: {
    index: 0,
    routes: [
      { key: '1', title: 'Income' },
      { key: '2', title: 'Outcome' },
      { key: '3', title: 'Balance' },
    ],
  },
};

export default handleActions({
  [CHANGE_TAB]: (state, action) => Object.assign(state, { index: action.payload.index }),
}, initialState);

Component

import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { View, StyleSheet } from 'react-native';
import { TabViewAnimated, TabViewPage, TabBarTop } from 'react-native-tab-view';

import * as actions from './actions';
import { NAME } from './constants';

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  tabbar: {
    backgroundColor: '#2196f3',
  },
  page: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
  },
  indicator: {
    backgroundColor: '#ffeb3b',
  },
  label: {
    color: '#fff',
    fontWeight: '400',
  },
});

class Layout extends Component {
  static propTypes = {
    style: View.propTypes.style,
    changeTab: PropTypes.func.isRequired,
    navigation: PropTypes.object.isRequired,
  };

  renderHeader = (props) => (
    <TabBarTop
      {...props}
      pressColor="rgba(0, 0, 0, .2)"
      indicatorStyle={styles.indicator}
      style={styles.tabbar}
      labelStyle={styles.label}
    />
  );

  renderScene = ({ route }) => {
    switch (route.key) {
      case '1':
        return <View style={[styles.page, { backgroundColor: '#ff4081' }]} />;
      case '2':
        return <View style={[styles.page, { backgroundColor: '#673ab7' }]} />;
      case '3':
        return <View style={[styles.page, { backgroundColor: '#4caf50' }]} />;
      default:
        return null;
    }
  };

  renderPage = (props) => (<TabViewPage {...props} renderScene={this.renderScene} />);

  render() {
    return (
      <TabViewAnimated
        style={[styles.container, this.props.style]}
        navigationState={this.props.navigation}
        renderScene={this.renderPage}
        renderHeader={this.renderHeader}
        onRequestChangeTab={this.props.changeTab}
      />
    );
  }
}

function mapStateToProps(state) {
  return {
    navigation: state[NAME].navigation,
  };
}

export default connect(mapStateToProps, actions)(Layout);

The weird part is that if I console.log the state index and the action index...

  [CHANGE_TAB]: (state, action) => {
    console.log('state index', state.navigation.index);
    console.log('action index', action.payload.index);
    return Object.assign(state.navigation, { index: action.payload.index });
  },

If I click (not even swipe) the second tab (index = 2) this is what gets logged

state index undefined
action index 1

Any clues?

Render TabBar separate from TabView

Hey great & under-appreciated library! If I want my TabBar to overlay the tabview (Like I have a custom navigation and want to render the TabBar underneath it), I get it working with TabBarTop using absolute positioning, but only when scrollEnabled. Anyone know the case, or a better approach? (My custom navigation & content are not easily separated into separate components)

Requires react-native >=0.36?

It would be nice to state somewhere that this requires react-native version at least 0.36 (or whenever was overflow: "scroll" introduced). On react-native 0.32, you get the infamous red box screen stating that View doesn't support overflow: "scroll", only "visible" or "hidden".

Unable to 'Cancel' swipes properly

Thanks for this very well written library. Very performant with a highly reusable API. One issue I did find with it is that its hard to cancel a swipe.

If you start a swipe in a direction, lets say to the right, and then attempt to cancel by reversing the swipe to the left - the left scene will still open (unless your swipe ends with no velocity and is past the threshold)

Can not be displayed on the Android phone

In _renderScene(), Android phones can not be a normal rendering of the component I want to render, such as a background color component, can normal rendering in iPhone, in the Android equipment but not normal rendering components contained in is not rendered.

Extra page without inserting the icon for TabBar

Howdy,

I have recently run into situation that I need an extra page/view that not include the icon into the TabBar, my current solution is adding an extra if as below

Line 303 in src/TabBar.js

{routes.map((route, i) => {
    const focused = index === i;
    const outputRange = inputRange.map(inputIndex => inputIndex === i ? 1 : 0.7);

Replace with

{routes.map((route, i) => {
   
    // skip if not required to display icon
    if (i.hideIcon) return;

    const focused = index === i;
    const outputRange = inputRange.map(inputIndex => inputIndex === i ? 1 : 0.7);

Then in the state setup

state = {
    index: 0,
    routes: [
        { key: '1', icon: 'home' },
        { key: '2', icon: 'magnify' },
        { key: '3', hideIcon: true }
    ]
};

Also setup as normal for 3 keys (as above example)

_renderScene = ({route}) => {
    switch (route.key) {
        ...
    }
}

Happy to do a PR if this sounds good.
Cheer!

returning undefined for renderHeader results in strange behavior

I have a TabViewAnimated component that switches between two routes, but does not show a TabBarTop until a state is triggered.

Behavior

  1. upon first load, returns undefined for renderHeader, as I don't need a header, just the first page from my state.routes
  2. once a certain state is changed to true, the header is then rendered, and a user can switch between both the routes with the header.
  3. if a user hides the TabViewAnimated component (via changing the opacity, as it's an overlay within another component in my app), then returns to the TabViewAnimated component, neither page gets rendered, just the header. jumping between tabs does nothing except trigger the material animation.

Current fix

My current fix is to not return undefined, but a wrapper with some styling that hides TabBarTop given some state. This seems to fix the issue and is fine, but I'm curious why returning undefined seems to mess up the component lifecycle.

renderHeader(props) {
    // if (this.props.hideTab) {
    //   return;
    // }
    const viewProps = this.props.hideTab ? {
        style: {
          height: 0,
          opacity: 0
        },
        pointerEvents: 'none'
      } : {};

    return (
      <View {...viewProps}>
        <TabBarTop/>
      </View>
    );
  }

Navigation within page

Hi there, thank you for this amazing plugin, I found it so much easy to implement quality navigator.

I run into a situation that need to navigate to different page within a page content instead of TabBar icons. I had a look through and did not find the solution, I hope that there is a way to do that

Cheers!

Exponent example doesn't work with yarn

So I'm attempting to get the example running on my local machine but am having trouble running the example on my iOS sim.

On the device I get the following error: undefined is not an object (evaluating 'meta.fileHashes')

And in Exponent logs I am getting the following stack:


Possible Unhandled Promise Rejection (id: 0):
null is not an object (evaluating 'prevComponentInstance._currentElement')
_updateRenderedComponent@http://packager.qv-ebq.julienvincent.example.exp.direct:80/main.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=exponent/tools/hashAssetFiles&includeAssetFileHashes=true:18782:46
_performComponentUpdate@http://packager.qv-ebq.julienvincent.example.exp.direct:80/main.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=exponent/tools/hashAssetFiles&includeAssetFileHashes=true:18763:30
updateComponent@http://packager.qv-ebq.julienvincent.example.exp.direct:80/main.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=exponent/tools/hashAssetFiles&includeAssetFileHashes=true:18682:29
performUpdateIfNecessary@http://packager.qv-ebq.julienvincent.example.exp.direct:80/main.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=exponent/tools/hashAssetFiles&includeAssetFileHashes=true:18590:21
performUpdateIfNecessary@http://packager.qv-ebq.julienvincent.example.exp.direct:80/main.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=exponent/tools/hashAssetFiles&includeAssetFileHashes=true:16775:42
runBatchedUpdates@http://packager.qv-ebq.julienvincent.example.exp.direct:80/main.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=exponent/tools/hashAssetFiles&includeAssetFileHashes=true:16386:41
perform@http://packager.qv-ebq.julienvincent.example.exp.direct:80/main.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=exponent/tools/hashAssetFiles&includeAssetFileHashes=true:17122:16
perform@http://packager.qv-ebq.julienvincent.example.exp.direct:80/main.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=exponent/tools/hashAssetFiles&includeAssetFileHashes=true:17122:16
flushBatchedUpdates@http://packager.qv-ebq.julienvincent.example.exp.direct:80/main.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=exponent/tools/hashAssetFiles&includeAssetFileHashes=true:16408:20
flushBatchedUpdates@[native code]
closeAll@http://packager.qv-ebq.julienvincent.example.exp.direct:80/main.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=exponent/tools/hashAssetFiles&includeAssetFileHashes=true:17188:19
perform@http://packager.qv-ebq.julienvincent.example.exp.direct:80/main.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=exponent/tools/hashAssetFiles&includeAssetFileHashes=true:17135:14
batchedUpdates@http://packager.qv-ebq.julienvincent.example.exp.direct:80/main.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=exponent/tools/hashAssetFiles&includeAssetFileHashes=true:34017:20
enqueueUpdate@http://packager.qv-ebq.julienvincent.example.exp.direct:80/main.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=exponent/tools/hashAssetFiles&includeAssetFileHashes=true:16436:32
enqueueUpdate@http://packager.qv-ebq.julienvincent.example.exp.direct:80/main.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=exponent/tools/hashAssetFiles&includeAssetFileHashes=true:28693:27
enqueueSetState@http://packager.qv-ebq.julienvincent.example.exp.direct:80/main.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=exponent/tools/hashAssetFiles&includeAssetFileHashes=true:28878:14
setState@http://packager.qv-ebq.julienvincent.example.exp.direct:80/main.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=exponent/tools/hashAssetFiles&includeAssetFileHashes=true:9780:29
_callee2$@http://packager.qv-ebq.julienvincent.example.exp.direct:80/main.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=exponent/tools/hashAssetFiles&includeAssetFileHashes=true:1604:15
tryCatch@http://packager.qv-ebq.julienvincent.example.exp.direct:80/main.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=exponent/tools/hashAssetFiles&includeAssetFileHashes=true:24056:33
invoke@http://packager.qv-ebq.julienvincent.example.exp.direct:80/main.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=exponent/tools/hashAssetFiles&includeAssetFileHashes=true:24330:20
tryCatch@http://packager.qv-ebq.julienvincent.example.exp.direct:80/main.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=exponent/tools/hashAssetFiles&includeAssetFileHashes=true:24056:33
invoke@http://packager.qv-ebq.julienvincent.example.exp.direct:80/main.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=exponent/tools/hashAssetFiles&includeAssetFileHashes=true:24132:20
http://packager.qv-ebq.julienvincent.example.exp.direct:80/main.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=exponent/tools/hashAssetFiles&includeAssetFileHashes=true:24140:7
tryCallOne@http://packager.qv-ebq.julienvincent.example.exp.direct:80/main.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=exponent/tools/hashAssetFiles&includeAssetFileHashes=true:26109:10
http://packager.qv-ebq.julienvincent.example.exp.direct:80/main.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=exponent/tools/hashAssetFiles&includeAssetFileHashes=true:26195:19
callTimer@http://packager.qv-ebq.julienvincent.example.exp.direct:80/main.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=exponent/tools/hashAssetFiles&includeAssetFileHashes=true:5562:9
callImmediatesPass@http://packager.qv-ebq.julienvincent.example.exp.direct:80/main.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=exponent/tools/hashAssetFiles&includeAssetFileHashes=true:5661:28
callImmediates@http://packager.qv-ebq.julienvincent.example.exp.direct:80/main.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=exponent/tools/hashAssetFiles&includeAssetFileHashes=true:5676:43
guard@http://packager.qv-ebq.julienvincent.example.exp.direct:80/main.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=exponent/tools/hashAssetFiles&includeAssetFileHashes=true:3875:3
__callImmediates@http://packager.qv-ebq.julienvincent.example.exp.direct:80/main.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=exponent/tools/hashAssetFiles&includeAssetFileHashes=true:3997:6
http://packager.qv-ebq.julienvincent.example.exp.direct:80/main.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=exponent/tools/hashAssetFiles&includeAssetFileHashes=true:3964:24
guard@http://packager.qv-ebq.julienvincent.example.exp.direct:80/main.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=exponent/tools/hashAssetFiles&includeAssetFileHashes=true:3875:3
invokeCallbackAndReturnFlushedQueue@http://packager.qv-ebq.julienvincent.example.exp.direct:80/main.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=exponent/tools/hashAssetFiles&includeAssetFileHashes=true:3962:6
invokeCallbackAndReturnFlushedQueue@[native code]

what does "export type ..." means

hello. I'm a newer of react coder.There are much code that like 'export type ...' or import type {some class}' . so , what does it means. I've searched a lot ,but I still have no idea about it.Thank you very much.

On state change not re-render

Hi Team,

If state change then It'll not render again, I need to re render content when state change.
Plz suggest me

Pan responder not working before new props received since 0.0.25

It looks like in 22a95a8 the underlying TabPageView component's _panHandlers are only set when props are received.

The result is that you can't change tabs by panning until new props are received. Once the props are updated, for example, by tapping a tab label in the header, you can then switch tabs via panning.

TestID for Automated UI Testing

I'm using XCodes Automated UI Testing for Fastlane Snapshots in RN.
Is it currently possible to add a accessibilityTraits="button" and testID prop to each tab button so I can reference it?

Active TabBar

I would like the second tab to be active when the app loads. Is that possible?

image

Long delay between tab transition end and tab header update

Hello,

In the example below, I use two methods for tab navigation:

  • Method 1: by clicking on tab headers ( using jumpToIndex)

  • Method 2: by swiping tab views (using onRequestChangeTab)

These two methods show different user experiences.

  • In case of method 1, the tab top bar is updated instantly to reflect tab change.

  • In case of method 2, there's a quite noticable and somewhat annoying delay between tab transition end and tab header update.

Note: Tested on a physical Android device via 'react-native run-android'

Due to this issue, now I have to disable tab view swiping.

Please advice how I can fix it.

class ExampleOfLongDelay extends React.Component {
  state = {
    index: 0,
    routes: [
      { key: '1', title: 'First', icon: 'ios-book' },
      { key: '2', title: 'Second', icon: 'ios-chatboxes' },
      { key: '3', title: 'Third', icon: 'ios-paper' },
    ],
  };

  _onChangeTabIndex = (index) => {
    this.setState({
      index,
    });
  };
  
  _renderLabel = ({ navigationState }) => ({ route, index }) => { 
    const selected = navigationState.index === index;
    
    return (
      <Text style={[ styles.label, selected ? styles.selected : styles.idle ]}>
        {route.title}
      </Text>
    );
  };

  _renderIcon = ({ navigationState }) => ({ route, index }: any) => {
    const selected = navigationState.index === index;
    
    return (
      <Icon
        name={selected ? route.icon : route.icon + '-outline'}
        size={24}
        style={[ selected ? styles.selected : styles.idle ]}
      />
    );
  };
  
   _renderPager = (props) => {
    return <TabViewPagerPan {...props} swipeEnabled={true} />;
  };
  
  _renderHeader = (props) => {
    return (
      <TabBarTop 
        {...props} 
        renderIcon={this._renderIcon(props)}
        renderLabel={this._renderLabel(props)}
        jumpToIndex={this._onChangeTabIndex}
        style={styles.tabbar} 
        activeOpacity={1}
      />
    )
  };
  
  _renderScene = ({route}) => {
    switch (route.key) {
      case '1':
        return <View style={[ styles.page, { backgroundColor: '#ff4081' } ]} />;
      case '2':
        return <View style={[ styles.page, { backgroundColor: '#673ab7' } ]} />;
      case '3':
        return <View style={[ styles.page, { backgroundColor: '#4caf50' } ]} />;
      default:
        return null;
    }
  };

  render() {
    return (
      <View style={styles.container}>
        <TabViewAnimated
          style={{ flex: 1 }}
          navigationState={this.state}
          renderScene={this._renderScene}
          renderHeader={this._renderHeader}
          renderPager={this._renderPager}
          onRequestChangeTab={this._onChangeTabIndex}
          initialLayout={initialLayout}
        />        
      </View>
    );
  }
}

var styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  page: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
  },  
  tabbar: {
    backgroundColor: '#008CC9',
  },
 idle: {
    backgroundColor: 'transparent',
    color: 'white',
  },
  selected: {
    backgroundColor: 'transparent',
    color: 'white',
  },
});

Thanks

TabBar is not rendering anything on RN 0.37

image

it is working fine on RN35

this is my code

_renderLabel = ({ route }) => (
    route.title ? <Text style={styles.tabLabel}>{route.title}</Text> : null
  );

  _renderHeader = (props) => {
    return (
      <TabBarTop
        {...props}
        indicatorStyle={{borderColor: 'green', borderWidth: 1}}
        labelStyle={{color: 'black'}}
        renderLabel={this._renderLabel}
        style={styles.tabbar}
      />
    );
  };

styles

tabLabel: {
    color: 'black',
    margin: 8,
    fontWeight: 'bold',
  },
tabbar:{
    backgroundColor: 'white',
    height: 30,
    borderColor: 'lightgray',
    borderWidth: 1,
    shadowColor: 'rgba(0, 0, 0, 0.12)',
    shadowOpacity: 0.8,
    shadowRadius: 2,
    shadowOffset: {
      height: 1,
      width: 2,
    },
  },

UX is inappropriate if one of the tabs contains ScrollView

Swiping between tabs feels not good if one of tabs contains scrollview: scrollview prevail on swiping too much, so it's hard to swipe between tabs instead of scrolling up and down.

Try to apply this piece of code to this library:

onPanResponderMove: (_, {dx, dy, x0, y0}) => {
  if (!this._axis) {
    const dx2 = dx * dx;
    const dy2 = dy * dy;
    if (dx2 + dy2 > 10) {
      this._axis = dx2 > dy2 ? 'x' : 'y';
    }
  }
}

This feels way better.

Problem with npm package

When I try to install the package with npm, I get the error:
npm ERR! cb() never called!
The package react-addons-shallow-compare is okay, the problem is with react-native-tab-view


my npm version: 3.10.8
my node version: v6.9.1

styling text label on a tab bar

It seems the texts automatically becomes capital letters even if they were small letters..
How do u keep the text labels small letter?

Top inset of TabBar (ScrollView)

Hi
When we want to set height of tab in order to fit the text, scrollview still have an top inset
You should add
automaticallyAdjustContentInsets={false}
to ScrollView of TabBar.js

Vertical TabViewAnimated

can you add for example param "horizontal" type bool in TabViewAnimated

If true, the Animated view's children are arranged horizontally in a row instead of vertically.

A onChange method?

First off, thanks @satya164! This project is amazing!

I had one question. I would like to have a callback that gets called before a scene is changed. My use case is that I have a header with an icon. And the source of that icon depends on which route my TabView is on.

As of now, I use the onRequestChangeTab callback, but then the image is not updated until the transition is finished.
I could potentially hacked it together by using onChangePosition. But I rather not.

So what do you think? Would you accept a PR for this?

Improve TouchableItem press animation

If you touch quickly on the tab item button, the ripple effect won't trigger, so what I suggest is that you put delayPressIn: 0 in defaultProps to give a nice feedback effect when the button is pressed, no matter how fast you touch the screen.

I think this should be the default in React Native, maybe some people don't want this, so it would be nice if there's a way to pass props to TouchableItem through TabBarTop and TabBar.

Great component btw ๐Ÿ‘

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.