Tab navigation on profile screen (think Instagram profile page) - react-native

In React Native, how can I add a tab navigation in the middle of a screen?
So far, I have got a stack-navigator managing the overall navigation for my app. As I understand it, react-navigation has a default tab component, however that component appears to be placed at the bottom of the screen, not in the middle as desired here. I am also not clear on how to integrate it given that I already have the stack-navigator.
Current Approach:
Doing it manually as follows. Is there a better approach using react navigation to automatically handle click-effects and slide-in animations?
// set the tab-index on press
selectTab = ( index ) => {
this.setState({
activeIndex: index,
})
}
// render the content for the selected tab
renderTabContent = () => {
if( this.state.activeIndex == 1 ) {
return(
<View>
<Text>
This is the first section
</Text>
</View>
)
}
else if( this.state.activeIndex == 2 ) {
return(
<View>
<Text>
This is the second section
</Text>
</View>
)
}
else if( this.state.activeIndex == 3 ) {
return(
<View>
<Text>
This is the third section
</Text>
</View>
)
}
}
render() {
return (
<ScrollView>
<View style={{ flexDirection: 'row' justifyContent: 'space-around' }}>
<TouchableOpacity
onPress={() =>this.selectTab(1)}
style={[this.state.activeIndex == 1 ? { backgroundColor: 'red', width: 100 } : { backgroundColor: 'gray', width: 100, }]}
>
<Text>#1</Text>
</TouchableOpacity>
<TouchableOpacity
onPress={() =>this.selectTab(2)}
style={[this.state.activeIndex == 2 ? { backgroundColor: 'red', width: 100 } : { backgroundColor: 'gray', width: 100, }]}
>
<Text>#2</Text>
</TouchableOpacity>
<TouchableOpacity
onPress={() =>this.selectTab(3)}
style={[this.state.activeIndex == 3 ? { backgroundColor: 'red', width: 100 } : { backgroundColor: 'gray', width: 100, }]}
>
<Text>#3</Text>
</TouchableOpacity>
</View>
{this.renderTabContent()}
</ScrollView>
)
}
PS: Are there any beginner-friendly examples of real-world(ish) apps to get an idea of how common UI patterns are implemented in React Native?

You can use createBottomTabNavigator function of react-navigation library:
import { createBottomTabNavigator } from 'react-navigation';
And then create your stackNavigator :
export default createBottomTabNavigator({
Home: { screen: Home},
Search: { screen: Search},
History: { screen: History }
});
You can customize your tab appearance and behavior by following this link.
https://reactnavigation.org/docs/en/tab-based-navigation.html#customizing-the-appearance
For complete example you can refer:
https://github.com/react-navigation/react-navigation-tabs

Related

How to change only the active button background color [React-Native]

import React, { Component } from 'react';
import { Text, View, StyleSheet, TouchableOpacity } from 'react-native';
class App extends Component {
constructor(props){
super(props)
this.state={
selected:null
}
}
handle=()=>{
this.setState({selected:1})
}
render() {
return (
<View>
<TouchableOpacity style={[styles.Btn, {backgroundColor:this.state.selected===1?"green":"white"}]} onPress={this.handle}>
<Text style={styles.BtnText}>Button 1</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.Btn} onPress={this.handle}>
<Text style={styles.BtnText}>Button 2</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.Btn} onPress={this.handle}>
<Text style={styles.BtnText}>Button 3</Text>
</TouchableOpacity>
</View>
);
}
}
const styles = StyleSheet.create({
Btn: {
borderWidth: 1,
width: 100,
height: 20,
borderRadius: 8,
margin: 5,
padding: 10,
justifyContent: 'center',
flexDirection: 'row',
alignItems: 'center',
},
BtnText: {
fontSize: 15,
},
});
export default App;
Snack Link : https://snack.expo.dev/U_fX-6Tao-
I want to make it so when I click a button, the active button backgroundColor should change to "green" and text to "white" and the rest of the buttons backgroundColor and textColor should stay "red". But when I click another button then that button should become active and the previous active button should get back to its normal state.
It would be wonderful if you could also explain the logic behind it as I'm a newbie in React Native.
Thank you.
You are always setting the active button to the first one. Also, I would use an array to render the buttons. I would do something like this:
class App extends Component {
constructor(props){
super(props)
this.state = {
selected: null
}
}
handlePress = (item) => {
this.setState({ selected: item })
}
render() {
return (
<View>
{[...Array(3).keys()].map((item) => {
return (
<TouchableOpacity key={item} style={[styles.Btn, {backgroundColor: this.state.selected === item ? "green" : "white" }]} onPress={() => this.handlePress(item)}>
<Text style={styles.BtnText}>Button {item + 1}</Text>
</TouchableOpacity>
)
})}
</View>
);
}
}
I created an Themed component(OK I did not create it. It is there when I create the app with Expo).
import { useState } from 'react';
import { TouchableOpacity as DefaultTouchableOpacity } from 'react-native';
export type TouchableProps = DefaultTouchableOpacity['props'] & { activeBgColor?: string };
export function TouchableOpacity(props: TouchableProps) {
const [active, setActive] = useState(false);
const { style, activeBgColor, ...otherProps } = props;
if (activeBgColor) {
return (
<DefaultTouchableOpacity
style={[style, active ? { backgroundColor: activeBgColor } : {}]}
activeOpacity={0.8}
onPressIn={() => setActive(true)}
onPressOut={() => setActive(false)}
{...otherProps}
/>
);
}
return <DefaultTouchableOpacity style={style} activeOpacity={0.8} {...otherProps} />;
}
Then I use this TouchableOpacity everywhere.
<TouchableOpacity
style={tw`rounded-sm h-10 px-2 justify-center items-center w-1/5 bg-sky-400`}
activeBgColor={tw.color('bg-sky-600')}
>
<Text style={tw`text-white font-bold`}>a Button</Text>
</TouchableOpacity>
Oh I am writing TailwindCSS with twrnc by the way. You will love it.
See the screenshot below.

SafeAreaView does not take the whole screen after placing on the background

I am using RN 0.59. Every time I placed the app on the background then reopen it immediately, the SafeAreaView does not take the whole screen.
But, if I placed the app on the background and reopen it after a while (for about 3 seconds) it is working fine.
Here's my snippet on SafeAreaView
...
const SafeAreaViewUI = ({children}) => {
return (
<SafeAreaView style={styles.container}>
<View style={styles.content}>
{ children }
</View>
</SafeAreaView>
);
};
...
export default SafeAreaViewUI;
for my styling
container: {
flex: 1,
backgroundColor: Platform.OS === 'android' ? blurple : null,
paddingTop: Platform.OS === 'android' ? StatusBar.currentHeight : 0,
},
content: {
flexGrow: 1,
color: text,
backgroundColor: white,
}
Any insight for this one?
This worked on my end.
const SafeAreaViewUI = ({children}) => {
const [ height, setHeight ] = useState(0)
return (
<SafeAreaView
onLayout={() => {
if (Platform.OS === 'android') {
setHeight(StatusBar.currentHeight || 0);
}
}}
style={[styles.container, { paddingTop: height }]}>
<View style={styles.content}>
{ children }
</View>
</SafeAreaView>
);
If there's other possible answer. Kindly post it here.
Thanks!

Build a custom seconds and minutes input in React Native?

Im making an exercise timer and I need an input for seconds and minutes.
The ideal UX would 2 select lists side by side. Tapping on the input would open both lists, so different from the web where you can only focus on one at a time.
Ive made a demo here, although it would need to be in a modal or similar:
https://snack.expo.io/#jamesweblondon/intelligent-apple
import * as React from 'react';
import { Text, View, StyleSheet, ScrollView, TouchableOpacity } from 'react-native';
const minutes = new Array(10).fill('').map((item, index)=>{
return index;
})
const seconds = new Array(60).fill('').map((item, index)=>{
return index;
})
const Item = ({text, isSelected, onPress}) => {
return(
<TouchableOpacity
onPress={onPress}
style={[styles.item, isSelected && styles.itemIsSelected]}>
{text}
</TouchableOpacity>
)
}
export default function App() {
const [selectedMinute, setSelectedMinute] = React.useState(1);
const [selectedSeconds, setSelectedSeconds] = React.useState(10);
return (
<View style={styles.container}>
<View style={styles.row}>
<Text style={styles.heading}>Minutes</Text>
<ScrollView style={styles.scroll}>
{
minutes.map((item, index)=>{
return <Item
onPress={()=>setSelectedMinute(item)}
text={item}
key={item}
isSelected={selectedMinute === index}
/>
})
}
</ScrollView>
</View>
<View style={styles.row}>
<Text style={styles.heading}>Seconds</Text>
<ScrollView style={styles.scroll}>
{
seconds.map((item, index)=>{
return <Item
onPress={()=>setSelectedSeconds(item)}
text={item}
key={item}
isSelected={selectedSeconds === index}
/>
})
}
</ScrollView>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
height: 300
},
row: {
flex: 1,
},
scroll: {
height: 300
},
heading: {
fontSize: 20,
fontWeight: 'bold',
textAlign: 'center'
},
item: {
padding: 30,
backgroundColor: 'grey',
borderColor: 'white',
borderWidth: 1,
justifyContent: 'center',
alignItems: 'center'
},
itemIsSelected: {
backgroundColor: 'gold'
}
});
Is it OK to build your own inputs in React Native? This would be a a bad idea on the web as it would probably result in worse UX (especially for keyboard navigation) and a component that screen readers wouldn't know how to use.
I couldn't find a library that does quite what I need. This is close but you can only have one list open at a time. It also had a major bug with Redux making it unusable for me:
https://github.com/lawnstarter/react-native-picker-select#readme
Ive been trying to use the Picker component as I imagine it's more semantic? It works how I need on iOS but not Android (see screenshots):

Make autoscroll viewpager in reactnative

I am trying to make the sliding image. I have followed the this . Its responding on sliding with fingers but not sliding automatically. How can I do so? I have implemented as follows:
<ViewPager style={styles.viewPager} initialPage={0}>
<View key="1">
<Image style={ {height: '100%', width:'100%'}}source={{uri :'https://images.unsplash.com/photo-1441742917377-57f78ee0e582?h=1024'}}></Image>
</View>
<View key="2">
<Image style={ {height: '100%', width:'100%'}}source={{uri :'https://images.unsplash.com/photo-1441716844725-09cedc13a4e7?h=1024'}}></Image>
</View>
</ViewPager>
By looking at the source code I have found there is a method setPage() which accepts page number as argument.
Look at the example code they have provided where you can find how to use reference and call setPage method Example
Now you can use setInterval() and make auto slide work.
setInterval(() => {
this.viewPager.current.setPage(page);
}, 1000);
Set page is a method to update the page of the viewpager. You can autoscroll the viewpager using a timer and by updating the pager by using the setPage method. Below is the complete code for the same.
import React, { Component } from 'react';
import { StyleSheet, View, Text, Platform } from 'react-native';
import ViewPager from '#react-native-community/viewpager';
export default class App extends Component {
state = {
pageNumber: 0,
totalPage: 2
}
componentDidMount() {
var pageNumber = 0;
setInterval(() => {
if (this.state.pageNumber >= 2) {
pageNumber = 0;
} else {
pageNumber = this.state.pageNumber;
pageNumber++;
}
console.log(pageNumber)
this.setState({ pageNumber: pageNumber })
this.viewPager.setPage(pageNumber)
}, 2000);
}
render() {
return (
<View style={{ flex: 1 }}>
<ViewPager style={styles.viewPager} initialPage={0}
ref={(viewPager) => { this.viewPager = viewPager }}
scrollEnabled={true}>
<View key="1">
<Text style={{ color: 'black' }}>First page</Text>
</View>
<View key="2">
<Text>Second page</Text>
</View>
<View key="3">
<Text>Third page</Text>
</View>
</ViewPager>
</View>
);
}
}
const styles = StyleSheet.create({
viewPager: {
flex: 1,
},
});

Missing styles for item in React Native Side Menu (should indicate active route in React Native Navigator)

I'm working on an app with a Side Menu and a Navigator. In the side menu there are menu-items which let's you navigate (using Navigator), and the menu-items also get styled to indicate which one is active.
When first going to a new route with navigator.push() and then going back with navigator.pop(), the menu-item corresponding to the previously active route (which is now inactive) does not get either of the styles to show that it is either inactive or active. The style of the menu item (RouteMenuItem in the full example below) is as follows
style={[
{ padding: 15, borderColor: 'firebrick', borderWidth: 1 },
(isActive
? {backgroundColor: 'green'}
: {opacity: 0.5}
)
]}
How is it possible that neither {backgroundColor: 'green'} nor {opacity: 0.5} gets applied?
The image below shows how the bug looks on Android: "Route ONE" is selected, however, the menu item for "Route TWO" does not have opacity: 0.5 as it should (it should be half transparent like the 3rd menu item).
Below is the full code for a minimal example to reproduce the bug. The component hierarchy is like this:
<Navigator renderScene={() => <SideMenu><View /></SideMenu>} />
PS: We use StyleSheet.create() in our code, but in the example below I've just defined the styles inline. It does not seem to make any difference.
import React from 'react';
import {View, Text, TouchableHighlight, Navigator, Dimensions} from 'react-native';
import SideMenu from 'react-native-side-menu';
/***************
/** Routes **/
/****************/
const ROUTES = {
ONE: () => ({ title: 'Route ONE' }),
TWO: () => ({ title: 'Route TWO' }),
THREE: () => ({ title: 'Route THREE' }),
};
/**************/
/** Main **/
/**************/
export default class Main extends React.Component {
render() {
return (
<Navigator
style={{flex: 1}}
initialRouteStack={[
ROUTES.ONE(),
]}
renderScene={(route, navigator) =>
<Scene route={route} navigator={navigator} />
}
/>
);
}
}
/***************/
/** Scene **/
/***************/
class Scene extends React.Component {
state = {
menuIsOpen: false,
}
openMenu = () => {
this.setState({ menuIsOpen: true });
}
onMenuOpenChanged = (menuIsOpen) => {
if (this.state.menuIsOpen !== menuIsOpen) {
this.setState({ menuIsOpen });
}
}
render() {
const {route, navigator} = this.props;
return (
<View style={{flex: 1}}>
<SideMenu
menu={
<Menu
route={route}
navigator={navigator}
/>
}
menuPosition="right"
bounceBackOnOverdraw={false}
onChange={this.onMenuOpenChanged}
isOpen={this.state.menuIsOpen}
openMenuOffset={Dimensions.get('window').width * 0.75}
disableGestures={false}
>
<Screen route={route} navigator={navigator} openMenu={this.openMenu} menuIsOpen={this.state.menuIsOpen} />
</SideMenu>
</View>
);
}
}
/**************/
/** Menu **/
/**************/
class Menu extends React.Component {
render() {
const {route, navigator} = this.props;
return (
<View style={{ flex: 1, backgroundColor: 'coral', paddingTop: 25 }}>
<Text>Currently at {route.title}</Text>
<RouteMenuItem forRoute={ROUTES.ONE()} route={route} navigator={navigator} />
<RouteMenuItem forRoute={ROUTES.TWO()} route={route} navigator={navigator} />
<RouteMenuItem forRoute={ROUTES.THREE()} route={route} navigator={navigator} />
</View>
);
}
}
const RouteMenuItem = ({forRoute, route, navigator}) => (
<TouchableHighlight onPress={() => navigator.push(forRoute)}>
<Text style={[ { padding: 15, borderColor: 'firebrick', borderWidth: 1 }, (forRoute.title === route.title ? {backgroundColor: 'green'} : {opacity: 0.5}) ]}>
Go to {forRoute.title} ({(forRoute.title === route.title ? 'current route' : 'NOT CURRENT ROUTE')})
</Text>
</TouchableHighlight>
);
/*****************************/
/** Screen, inside Menu **/
/*****************************/
class Screen extends React.Component {
render() {
const {route, navigator, openMenu, menuIsOpen} = this.props;
return (
<View style={{ flex: 1 }}>
<View style={{ flexDirection: 'row', justifyContent: 'space-between', backgroundColor: 'peachpuff', paddingTop: 25 }}>
<HeaderButton onPress={navigator.pop}>Back</HeaderButton>
<HeaderButton onPress={openMenu}>Menu</HeaderButton>
</View>
<View style={{ flex: 1, backgroundColor: 'white' }}>
<Text style={{ margin: 50, fontSize: 50 }}>
{route.title}
</Text>
</View>
</View>
);
}
}
const HeaderButton = ({onPress, children}) => (
<TouchableHighlight underlayColor="green" onPress={onPress}>
<Text style={{ padding: 10, borderColor: 'firebrick', borderWidth: 1 }}>
{children}
</Text>
</TouchableHighlight>
);
The problem occurs becauase children of TouchableHighlight gets default opacity (1) after the TouchableHighlight is pressed. As that is a more specific problem I've asked a new question here.
In this case the bug appears when going back to a previous route, because the menu item in that instance of the Menu was pressed (moving us to a new route before we could spot the bug).