UNPKG

16.7 kBMarkdownView Raw
1# React Native Tab View
2
3[![Build Status][build-badge]][build]
4[![Version][version-badge]][package]
5[![MIT License][license-badge]][license]
6
7A cross-platform Tab View component for React Native.
8
9- [Run the example app to see it in action](https://expo.io/@satya164/react-native-tab-view-demos).
10- Checkout the [example/](https://github.com/react-native-community/react-native-tab-view/tree/master/example) folder for source code.
11
12## Features
13
14- Smooth animations and gestures
15- Scrollable tabs
16- Supports both top and bottom tab bars
17- Follows Material Design spec
18- Highly customizable
19- Fully typed with [Flow](https://flow.org/)
20
21## Demo
22
23<a href="https://raw.githubusercontent.com/satya164/react-native-tab-view/master/demo/demo.mp4"><img src="https://raw.githubusercontent.com/satya164/react-native-tab-view/master/demo/demo.gif" width="360"></a>
24
25## Installation
26
27Open a Terminal in the project root and run:
28
29```sh
30yarn add react-native-tab-view
31```
32
33If you are using Expo, you are done. Otherwise, continue to the next step.
34
35Install and link [`react-native-gesture-handler`](https://github.com/kmagiera/react-native-gesture-handler) and [`react-native-reanimated`](https://github.com/kmagiera/react-native-reanimated). To install and link them, run:
36
37```sh
38yarn add react-native-reanimated react-native-gesture-handler
39react-native link react-native-reanimated
40react-native link react-native-gesture-handler
41```
42
43**IMPORTANT:** There are additional steps required for `react-native-gesture-handler` on Android after running `react-native link react-native-gesture-handler`. Check the [this guide](https://kmagiera.github.io/react-native-gesture-handler/docs/getting-started.html) to complete the installation.
44
45## Quick Start
46
47```js
48import * as React from 'react';
49import { View, StyleSheet, Dimensions } from 'react-native';
50import { TabView, SceneMap } from 'react-native-tab-view';
51
52const FirstRoute = () => (
53 <View style={[styles.scene, { backgroundColor: '#ff4081' }]} />
54);
55const SecondRoute = () => (
56 <View style={[styles.scene, { backgroundColor: '#673ab7' }]} />
57);
58
59export default class TabViewExample extends React.Component {
60 state = {
61 index: 0,
62 routes: [
63 { key: 'first', title: 'First' },
64 { key: 'second', title: 'Second' },
65 ],
66 };
67
68 render() {
69 return (
70 <TabView
71 navigationState={this.state}
72 renderScene={SceneMap({
73 first: FirstRoute,
74 second: SecondRoute,
75 })}
76 onIndexChange={index => this.setState({ index })}
77 initialLayout={{ width: Dimensions.get('window').width }}
78 />
79 );
80 }
81}
82
83const styles = StyleSheet.create({
84 scene: {
85 flex: 1,
86 },
87});
88```
89
90[Try this example on Snack](https://snack.expo.io/@satya164/react-native-tab-view-quick-start)
91
92## More examples on Snack
93
94- [Custom Tab Bar](https://snack.expo.io/@satya164/react-native-tab-view-custom-tabbar)
95- [Lazy Load](https://snack.expo.io/@satya164/react-native-tab-view-lazy-load)
96
97## Integration with React Navigation
98
99React Navigation integration can be achieved by the [react-navigation-tabs](https://github.com/react-navigation/react-navigation-tabs) package. Note that while it's easier to use, it is not as flexible as using the library directly.
100
101## API reference
102
103The 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.
104
105### `TabView`
106
107Container component responsible for rendering and managing tabs. Follows material design styles by default.
108
109Basic usage look like this:
110
111```js
112<TabView
113 navigationState={this.state}
114 onIndexChange={index => this.setState({ index })}
115 renderScene={SceneMap({
116 first: FirstRoute,
117 second: SecondRoute,
118 })}
119/>
120```
121
122#### Props
123
124##### `navigationState` (`required`)
125
126State for the tab view. The state should contain the following properties:
127
128- `index`: a number representing the index of the active route in the `routes` array
129- `routes`: an array containing a list of route objects used for rendering the tabs
130
131Each route object should contain the following properties:
132
133- `key`: a unique key to identify the route (required)
134- `title`: title for the route to display in the tab bar
135- `icon`: icon for the route to display in the tab bar
136- `accessibilityLabel`: accessibility label for the tab button
137- `testID`: test id for the tab button
138
139Example:
140
141```js
142{
143 index: 1,
144 routes: [
145 { key: 'music', title: 'Music' },
146 { key: 'albums', title: 'Albums' },
147 { key: 'recents', title: 'Recents' },
148 { key: 'purchased', title: 'Purchased' },
149 ]
150}
151```
152
153`TabView` is a controlled component, which means the `index` needs to be updated via the `onIndexChange` callback.
154
155##### `onIndexChange` (`required`)
156
157Callback which is called on tab change, receives the index of the new tab as argument.
158The navigation state needs to be updated when it's called, otherwise the change is dropped.
159
160##### `renderScene` (`required`)
161
162Callback which returns a react element to render as the page for the tab. Receives an object containing the route as the argument:
163
164```js
165renderScene = ({ route, jumpTo }) => {
166 switch (route.key) {
167 case 'music':
168 return <MusicRoute jumpTo={jumpTo} />;
169 case 'albums':
170 return <AlbumsRoute jumpTo={jumpTo} />;
171 }
172};
173```
174
175You 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.
176
177`SceneMap` takes an object with the mapping of `route.key` to React components and returns a function to use with `renderScene` prop.
178
179```js
180import { SceneMap } from 'react-native-tab-view';
181
182...
183
184renderScene = SceneMap({
185 music: MusicRoute,
186 albums: AlbumsRoute,
187});
188```
189
190Specifying the components this way is easier and takes care of implementing a `shouldComponentUpdate` method.
191
192Each scene receives the following props:
193
194- `route`: the current route rendered by the component
195- `jumpTo`: method to jump to other tabs, takes a `route.key` as it's argument
196
197The `jumpTo` method can be used to navigate to other tabs programmatically:
198
199```js
200this.props.jumpTo('albums');
201```
202
203All 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`.
204
205**IMPORTANT:** **Do not** pass inline functions to `SceneMap`, for example, don't do the following:
206
207```js
208SceneMap({
209 first: () => <FirstRoute foo={this.props.foo} />,
210 second: SecondRoute,
211});
212```
213
214Always 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.
215
216##### `renderTabBar`
217
218Callback which returns a custom React Element to use as the tab bar:
219
220```js
221import { TabBar } from 'react-native-tab-view';
222
223...
224
225renderTabBar = props => <TabBar {...props} />;
226```
227
228If 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.
229
230```js
231renderTabBar = () => null;
232```
233
234##### `tabBarPosition`
235
236Position of the tab bar in the tab view. Possible values are `'top'` and `'bottom'`. Defaults to `'top'`.
237
238##### `lazy`
239
240Boolean indicating 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, set `lazy` to `true`.
241
242When you enable `lazy`, the unfocused screens will usually take some time to render when they come into focus. You can use the `renderLazyPlaceholder` prop to customize what the user sees during this short period.
243
244##### `renderLazyPlaceholder`
245
246Callback 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.
247
248This view is usually only shown for a split second. Keep it lightweight.
249
250By default, this renders `null`.
251
252##### `keyboardDismissMode`
253
254String indicating whether the keyboard gets dismissed in response to a drag gesture. Possible values are:
255
256- `'on-drag'` (default): the keyboard is dismissed when a drag begins.
257- `'none'`: drags do not dismiss the keyboard.
258
259##### `swipeEnabled`
260
261Boolean 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.
262
263##### `swipeDistanceThreshold`
264
265Minimum swipe distance which triggers a tab switch. By default, this is automatically determined based on the screen width.
266
267##### `swipeVelocityThreshold`
268
269Minimum swipe velocity which triggers a tab switch. Defaults to `1200`.
270
271##### `onSwipeStart`
272
273Callback which is called when the swipe gesture starts, i.e. the user touches the screen and moves it.
274
275##### `onSwipeEnd`
276
277Callback which is called when the swipe gesture ends, i.e. the user lifts their finger from the screen after the swipe gesture.
278
279##### `initialLayout`
280
281Object 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:
282
283```js
284{ width: Dimensions.get('window').width }}
285```
286
287##### `sceneContainerStyle`
288
289Style to apply to the view wrapping each screen. You can pass this to override some default styles such as overflow clipping:
290
291##### `style`
292
293Style to apply to the tab view container.
294
295### `TabBar`
296
297Material 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.
298
299For example, to customize the indicator color and the tab bar background color, you can pass `indicatorStyle` and `style` props to the `TabBar` respectively:
300
301```js
302renderTabBar={props =>
303 <TabBar
304 {...props}
305 indicatorStyle={{ backgroundColor: 'white' }}
306 style={{ backgroundColor: 'pink' }}
307 />
308}
309```
310
311#### Props
312
313##### `getLabelText`
314
315Function which takes an object with the current route and returns the label text for the tab. Uses `route.title` by default.
316
317```js
318getLabelText={({ route }) => route.title}
319```
320
321##### `getAccessible`
322
323Function which takes an object with the current route and returns a boolean to indicate whether to mark a tab as `accessible`. Defaults to `true`.
324
325##### `getAccessibilityLabel`
326
327Function 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.
328
329```js
330getAccessibilityLabel={({ route }) => route.accessibilityLabel}
331```
332
333##### `testID`
334
335Function 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.
336
337```js
338getTestID={({ route }) => route.testID}
339```
340
341getAccessibilityLabel: (props: { route: T }) => string;
342Get accessibility label for the tab button. This is read by the screen reader when the user taps the tab.
343Uses `route.accessibilityLabel` by default if specified, otherwise uses the route title.
344
345getTestID: (props: { route: T }) => string | undefined;
346Get the id to locate this tab button in tests, uses `route.testID` by default.
347
348##### `renderIcon`
349
350Function which takes an object with the current route, focused status and color and returns a custom React Element to be used as a icon.
351
352```js
353renderIcon={({ route, focused, color }) => (
354 <Icon
355 name={focused ? 'abums' : 'albums-outlined'}
356 color={color}
357 />
358)}
359```
360
361##### `renderLabel`
362
363Function which takes an object with the current route, focused status and color and returns a custom React Element to be used as a label.
364
365```js
366renderLabel={({ route, focused, color }) => (
367 <Text style={{ color, margin: 8 }}>
368 {route.title}
369 </Text>
370)}
371```
372
373##### `renderIndicator`
374
375Function which takes an object with the current route and returns a custom React Element to be used as a tab indicator.
376
377##### `renderBadge`
378
379Function which takes an object with the current route and returns a custom React Element to be used as a badge.
380
381##### `onTabPress`
382
383Function to execute on tab press. It receives the scene for the pressed tab, useful for things like scroll to top.
384
385##### `onTabLongPress`
386
387Function to execute on tab long press, use for things like showing a menu with more options
388
389##### `activeColor`
390
391Custom color for icon and label in the active tab.
392
393##### `inactiveColor`
394
395Custom color for icon and label in the inactive tab.
396
397##### `pressColor`
398
399Color for material ripple (Android >= 5.0 only).
400
401##### `pressOpacity`
402
403Opacity for pressed tab (iOS and Android < 5.0 only).
404
405##### `scrollEnabled`
406
407Boolean indicating whether to enable scrollable tabs.
408
409##### `bounces`
410
411Boolean indicating whether the tab bar bounces when scrolling.
412
413##### `tabStyle`
414
415Style to apply to the individual tab items in the tab bar.
416
417##### `indicatorStyle`
418
419Style to apply to the active indicator.
420
421##### `labelStyle`
422
423Style to apply to the tab item label.
424
425##### `contentContainerStyle`
426
427Style to apply to the inner container for tabs.
428
429##### `style`
430
431Style to apply to the tab bar container.
432
433## Optimization Tips
434
435### Avoid unnecessary re-renders
436
437The `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` in your route components to prevent unnecessary re-renders.
438
439For example, instead of:
440
441```js
442renderScene = ({ route }) => {
443 switch (route.key) {
444 case 'home':
445 return (
446 <View style={styles.page}>
447 <Avatar />
448 <NewsFeed />
449 </View>
450 );
451 default:
452 return null;
453 }
454};
455```
456
457Do the following:
458
459```js
460renderScene = ({ route }) => {
461 switch (route.key) {
462 case 'home':
463 return <HomeComponent />;
464 default:
465 return null;
466 }
467};
468```
469
470Where `<HomeComponent />` is a `PureComponent`:
471
472```js
473export default class HomeComponent extends React.PureComponent {
474 render() {
475 return (
476 <View style={styles.page}>
477 <Avatar />
478 <NewsFeed />
479 </View>
480 );
481 }
482}
483```
484
485### Avoid one frame delay
486
487We 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.
488
489For example, pass the following `initialLayout` to `TabView`:
490
491```js
492const initialLayout = {
493 height: 0,
494 width: Dimensions.get('window').width,
495};
496```
497
498The tab view will still react to changes in the dimension and adjust accordingly to accommodate things like orientation change.
499
500### Optimize large number of routes
501
502If 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.
503
504For example, do the following to render only 2 routes on each side:
505
506```js
507renderScene = ({ route }) => {
508 if (Math.abs(this.state.index - this.state.routes.indexOf(route)) > 2) {
509 return <View />;
510 }
511
512 return <MySceneComponent route={route} />;
513};
514```
515
516### Avoid rendering TabView inside ScrollView
517
518Nesting the `TabView` inside a vertical `ScrollView` will disable the optimizations in the `FlatList` components rendered inside the `TabView`. So avoid doing it if possible.
519
520## Contributing
521
522While developing, you can run the [example app](/example/README.md) to test your changes.
523
524Make sure your code passes Flow and ESLint. Run the following to verify:
525
526```sh
527yarn flow
528yarn lint
529```
530
531To fix formatting errors, run the following:
532
533```sh
534yarn lint -- --fix
535```
536
537Remember to add tests for your change if possible.
538
539<!-- badges -->
540
541[build-badge]: https://img.shields.io/circleci/project/github/react-native-community/react-native-tab-view/master.svg?style=flat-square
542[build]: https://circleci.com/gh/react-native-community/react-native-tab-view
543[version-badge]: https://img.shields.io/npm/v/react-native-tab-view.svg?style=flat-square
544[package]: https://www.npmjs.com/package/react-native-tab-view
545[license-badge]: https://img.shields.io/npm/l/react-native-tab-view.svg?style=flat-square
546[license]: https://opensource.org/licenses/MIT