How to nest tab navigation under container in React Native? - reactjs

How do I create tabbed navigations like the image shown? Using tabBarPosition it seems I can only set the tab bar to 'top' or 'bottom' of the entire screen? Is there any way to do something similar but within a container? I'm using https://reactnavigation.org/, but if you have other suggestions as well that'd be great (a library that is easy to use, and can allow me to apply custom styles for headers, buttons etc easily).

You might need custom navigator by yourself.
Here is the docs: https://reactnavigation.org/docs/navigators/custom
And here is the example how to do that:
https://github.com/react-community/react-navigation/blob/master/examples/NavigationPlayground/js/CustomTabs.js

Following #Tyreal Gray's advise, I created a custom navigator.
Here is some sample code.
const CustomTabBar = ({ navigation }) => {
let selectedIndex = navigation.state.index;
let isSelected = false;
const { routes } = navigation.state;
return (
<View style={styles.tabContainer}>
{routes.map((route, index) => {
selectedIndex === index ? isSelected = true : isSelected = false;
let textColor = isSelected ? 'blue' : 'gray';
return (
<TouchableOpacity
onPress={() => navigation.navigate(route.routeName)}
style={styles.tab}
key={route.routeName}
>
<Text style = {{color: textColor}}>{route.routeName}</Text>
</TouchableOpacity>
)
})}
</View>
);
};
const CustomTabView = ({ router, navigation }) => {
const { routes, index } = navigation.state;
const ActiveScreen = router.getComponentForState(navigation.state);
return (
<KeyboardAwareScrollView>
<View style={styles.container}>
<CustomTabBar navigation={navigation} />
<ActiveScreen
navigation={addNavigationHelpers({
...navigation,
state: routes[index],
})}
/>
</View>
</KeyboardAwareScrollView>
);
};
const CustomTabRouter = TabRouter(
{
Details: {
screen: DetailsScreen,
path: '',
},
Media: {
screen: MediaScreen,
path: 'media',
},
Reviews: {
screen: CreateReviewFlow,
path: 'reviews',
},
},
{
// Change this to start on a different tab
initialRouteName: 'Details',
}
);
const CustomTabs = createNavigationContainer(
createNavigator(CustomTabRouter)(CustomTabView)
);

Related

How do i implement react-dnd-text-dragpreview in a Functional Component?

i'm using react-dnd and i'm able to put an image when draging, but now i would like to instead of a image i want a custom text.
i found this component react-dnd-text-dragpreview, but the example is for react class component.
i've tried to put "dragPreviewImage" in the src of "DragPreviewImage" , but doesn't work.
can someone help me on this ?
thanks in advance !
https://www.npmjs.com/package/react-dnd-text-dragpreview
sample code
...
import { DragPreviewImage, useDrag } from 'react-dnd';
import { boxImage } from '../components/boxImage';
import { createDragPreview } from 'react-dnd-text-dragpreview'
function FieldDrag({ field, dropboxField, onDragEnd = () => null, setFieldValue = () => null, cargoCategories }) {
const [{ isDragging }, drag, preview] = useDrag(() => ({
type: 'field',
item: { id: field.id, dragCargoInfoId: field.dragCargoInfoId, dragCargoInfo: field.childDragCargoInfo },
collect: (monitor) => ({
isDragging: !!monitor.isDragging(),
}),
end: (item) => endDrag(item),
}));
const endDrag = (item) => {
onDragEnd(item);
};
const styles = {
fontSize: '12px'
}
const dragPreviewImage = createDragPreview('Custom Drag Text', styles);
.....
return (
<>
<DragPreviewImage connect={preview} src={boxImage} />
<span ref={drag} className="flex-item" style={{ ...computedStyle, ...styleDropboxField }}>
{getField(field, extraStyle, isboxed, cargoCategories)}
</span>
</>
);
drag with image
I found the solution in the codesandbox --> https://codesandbox.io/s/uoplz?file=/src/module-list.jsx:2522-2619
just put src image "dragPreviewImage.src".
<DragPreviewImage connect={preview} src={dragPreviewImage &&
dragPreviewImage.src} />

change backgroundcolor of button in flatlist onpress

I have some buttons in my flatlist like below
const renderItem = ({ item }) => <Item name={item.name} slug={item.slug} />;
const Item = ({ name, slug }) => {
return (
<TouchableOpacity
delayPressIn={0}
onPress={() => {
dispatch(setLanguage(slug));
}}
>
<View
style={[
styles.item,
{ backgroundColor: languages == slug ? "#940062" : "black" },
]}
>
<Text style={styles.title}>{name}</Text>
</View>
</TouchableOpacity>
);
};
return (
<SafeAreaView style={styles.container}>
<FlatList
horizontal={true}
data={jsonLang}
renderItem={renderItem}
keyExtractor={(item) => item.id.toString()}
/>
</SafeAreaView>
);
};
The above code works fine, when I click it is changing the background? But background color change is delayed by 1 second. Is this the right approach to change the background color of the button?
Thank you.
P.S: the setlanguage is my reducer in my redux
setLanguage: (state, action) => {
state.language = action.payload;
},
It works pretty fast, here's an example I created.
Perhaps the delay is in the reducer. So I artificially slowed it down.
const setLanguage = (languages) => {
return (dispatch) => {
setTimeout(()=>{
dispatch({
type: "setLanguage",
value: languages
});
}, 1000) // <--------------------- delay
};
}
but now we need to make the style apply faster. I added another field to next_languages ​​state:
const initialState = {
languages: "1",
next_languages: "1"
}
and modified the code like this:
const setLanguage = (languages) => {
return (dispatch) => {
dispatch({ // <-------------- for apply style quiqly
type: "setNextLanguage",
value: languages
});
setTimeout(()=>{
dispatch({
type: "setLanguage",
value: languages
});
}, 1000)
};
}
also added the costant to the component:
const fastLang = languages === next_languages
? languages
: next_languages;
Finally, the styles are set like that and still quickly
{ backgroundColor: fastLang == slug ? "#940062" : "yellow" }
I think you can even get by with one variable (perhaps this contradicts the logic of work), but this is already refactoring.
I hope I could help.
the 1-second delay not depending on background color changing time, but it depends on what setLanguage do?
if setLanguage change the app Language this means the all component that uses this Selector will re-render
you can split background color in separator state, this will change background fast, but change language it still takes 1-second
solution with react state just for explanation (you can use Redux)
//add this line
const [selectedLanguage, setSelectedLanguage] = react.useState(languages);
const renderItem = ({ item }) => <Item name={item.name} slug={item.slug} />;
const Item = ({ name, slug }) => {
return (
<TouchableOpacity
delayPressIn={0}
onPress={() => {
setSelectedLanguage(slug); //add this line, will update color immediately
dispatch(setLanguage(slug));
}}
>
<View
style={[
styles.item,
//use selectedLanguage here
{ backgroundColor: selectedLanguage === slug ? "#940062" : "black" },
]}
>
<Text style={styles.title}>{name}</Text>
</View>
</TouchableOpacity>
);
};
};

TypeError: undefined is not an object (evaluating 'route.params')

I have a Drawer Navigator set up, with various screens.
On one screen, I have a FlatList with a simple TouchableOpacity like below:
<TouchableOpacity
onPress={() =>
navigation.navigate("ViewProject", {
screen: "viewProject",
params: { projectID: item.projectID },
})
}
>
</TouchableOpacity>
The navigation works, going to the viewProject screen, but no matter what I seem to try the route.params just doesn't seem to exist.
export default function viewProject({ route }) {
const projID = route.params?.projectID ?? 0; // ERRORS with TypeError: undefined is not an object (evaluating 'route.params')
console.log(projID);
}
I'm using the latest React Native, Navigation etc and TypeScript.
My only success is setting the initialRouteParams on the createStackNavigator that feeds into the drawer, and yet that's only accessible via navigation.state.params:
const screens = {
viewProject: {
screen: viewProject,
navigationOptions: ({ navigation }) => {
return {
headerTitle: () => (
<Header navigation={navigation} title={"View Project"} />
),
};
},
},
};
const viewProjectStack = createStackNavigator(screens, {
initialRouteParams: { projectID: 23 }, // This works, but it's static and I can't change it
});
Thanks in advance.
Stewart
You can pass like this:-
<Button
title="Go to Details"
onPress={() => {
/* 1. Navigate to the Details route with params */
navigation.navigate('Details', {
itemId: 86,
otherParam: 'anything you want here',
});
}}
/>
and receive like this:-
function DetailsScreen({ route, navigation }) {
/* 2. Get the param */
const { itemId } = route.params;
const { otherParam } = route.params;
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>itemId: {JSON.stringify(itemId)}</Text>
<Text>otherParam: {JSON.stringify(otherParam)}</Text>
);
}
source
Hope it helps!!!

How to pass a prop from one component to another component? (React Native)

I've a Custom Component for Tab View through which I can make dynamic tabs and below is the code for it.
TabView is the custom to make those custom tabs and Selected is the component for a single Tab.
How can I send a prop from TabView component to Selected component?
I know how to send props in a regular scenario, but I don't know to send one in this case.
I made this component from the below link:
https://medium.com/#abdelhalim.ahmed95/how-to-create-scrollable-and-dynamic-top-tabsbar-using-react-navigation-17ca52acbc51
export class TabView extends Component {
Tabs = navigation => {
let tabs=['A', 'B', 'C', 'D','E','F','G','H'];
tabs = tabs.reduce((val, tab) => {
val[tab] = {
screen: Selected
}
return val
}, {})
const bottomTabs = createMaterialTopTabNavigator(
{
...tabs
},
{
tabBarOptions: {
style: {
backgroundColor: Themes.colors.FC_PRIMARY,
},
indicatorStyle:{
height: 2,
backgroundColor: Themes.colors.TC_PRIMARY_LIGHT,
},
}
}
)
const Tabs = createAppContainer(bottomTabs);
return <Tabs />
}
render() {
const { navigation } = this.props;
return (
<View style={STYLES.tabView}>
{this.Tabs(navigation)}
</View>
);
}
}
export class Selected extends Component {
constructor(props){
super(props);
this.state = {
screen: '',
screenType: this.props.type
}
}
static navigationOptions = ({ navigation }) => {
return({
tabBarLabel: ({ focused }) => (
<View>
<View style={STYLES.secondaryTabLabel}>
<H3
type={ focused ? "light" : "disabled" }
text={navigation.state.routeName}
/>
</View>
</View>
)
})
};
screenIs = payload => {
this.setState({ screen: payload.state.routeName })
}
render() {
const { navigation } = this.props;
return (
<View style={{flex: 1}}>
<NavigationEvents onWillFocus={this.screenIs} />
<Text>{this.state.screen}</Text>
</View>
);
}
}
Use the following code,
val[tab] = {
screen: () => (<Selected val={val}/>) //in case if you want to send val as props
}
So the final code of yours will be,
export class TabView extends Component {
Tabs = navigation => {
let tabs=['A', 'B', 'C', 'D','E','F','G','H'];
tabs = tabs.reduce((val, tab) => {
val[tab] = {
screen: () => (<Selected val={val}/>), // for props
navigationOptions: {
title: 'Shows' // send anything here to get in navigationOptions
},
}
return val
}, {})
const bottomTabs = createMaterialTopTabNavigator(
{
...tabs
},
{
tabBarOptions: {
style: {
backgroundColor: Themes.colors.FC_PRIMARY,
},
indicatorStyle:{
height: 2,
backgroundColor: Themes.colors.TC_PRIMARY_LIGHT,
},
}
}
)
const Tabs = createAppContainer(bottomTabs);
return <Tabs />
}
render() {
const { navigation } = this.props;
return (
<View style={STYLES.tabView}>
{this.Tabs(navigation)}
</View>
);
}
}
export class Selected extends Component {
constructor(props){
super(props);
this.state = {
screen: '',
screenType: this.props.type
}
}
static navigationOptions = ({ navigation, navigationOptions }) => {
return({
tabBarLabel: ({ focused }) => (
<View>
<View style={STYLES.secondaryTabLabel}>
<H3
type={ focused ? "light" : "disabled" }
text={navigationOptions.title} // use here
/>
</View>
</View>
)
})
};
screenIs = payload => {
this.setState({ screen: payload.state.routeName })
}
render() {
const { navigation } = this.props;
return (
<View style={{flex: 1}}>
<NavigationEvents onWillFocus={this.screenIs} />
<Text>{this.state.screen}</Text>
</View>
);
}
}

States in TabStackNavigator?

It seems like TabNavigator doesn't have it's own state. Is there any way to use state or props?
I want to show the number of unread notification on the Notification TabIcon.
export default TabNavigator(
{
...
Noti: {
screen: NotificationStackNavigator,
},
...
},
{
navigationOptions: ({ navigation }) => ({
header: null,
tabBarIcon: ({ focused }) => {
const { routeName } = navigation.state;
let iconName;
switch (routeName) {
...
case 'Noti':
iconName = 'ios-notifications';
break;
...
}
...
if(iconName == 'ios-notifications') {
return (
<IconBadge
MainElement={
<Ionicons
name={iconName}
size={30}
style={{ marginBottom: -3 }}
color={focused ? Colors.tabIconSelected : Colors.tabIconDefault}/>
}
BadgeElement={
<Text style={{color:'#FFFFFF'}}>
{{this.state.notifications}} // my goal
</Text>
}
IconBadgeStyle={{
backgroundColor: '#f64e59',
position: 'absolute',
right:-10,
top:0,
}}
/>
);
}
...
Please let me know if anything is unclear. Thanks in advance
UPDATE I'm planning to refactor my TabNavigator. Is this what you're trying to say?
export default TabNavigator(
to
const MainTabNavigator = TabNavigator(
class MainTabNavigator extends Component {
state = {notification}
export default connect(...)(MainTabNavigator);
UPDATE 2 MainTabNavigator's Top Level component is another TabNavigator. Is there any other solution?
const RootTabNavigator = TabNavigator ({
Auth: {
screen: AuthStackNavigator,
},
Welcome: {
screen: WelcomeScreen,
},
Main: {
screen: MainTabNavigator,
},
You can pass additional custom props via screenprops
const TabNav = TabNavigator({
// config
});
<TabNav
screenProps={/* this prop will get passed to the screen components as this.props.screenProps */}
/>
The full documentation is here

Resources