Badge doesn't fit into bottom navigation in react native - react-native

I tried using the Badge from https://github.com/xotahal/react-native-material-ui, but the element doesn't properly fit into the bottom navigation:
Here's the code from AppNavigator.js:
import { createMaterialBottomTabNavigator } from "react-navigation-material-bottom-tabs";
import { Badge, Icon, Avatar } from "react-native-material-ui";
export const SignedIn = createMaterialBottomTabNavigator(
{
MeetingsScreen: {
screen: MeetingsScreen,
navigationOptions: {
tabBarLabel: "Заседания",
tabBarIcon: ({ focused }) => (
<Badge text="3">
<TabBarIcon
focused={focused}
name={
Platform.OS === "ios"
? `ios-people${focused ? "" : "-outline"}`
: "md-people"
}
/>
</Badge>
)
}
},
...
I tried wrapping the TabBarIcon itself into the badge, but it didn't help.
How to set up this element properly? Or is there a better library for that?

Create a custom tab bar icon component that wraps the tab bar icon inside a view of greater width and height of the icon and make sure the badge is within the view perimeter. See diagram below:

Related

Set Screen Title in ReactNative createBottomTabNavigator

I am trying to set the Screen Title of the Navigated Screen by using navigator. But it's not working/ changing and doesn't throw an exception either.
Scenario
I want to load some states from Redux and display nice Header Title in the Navigation Screen when the user clicks on the bottom Tab.
User clicks on second Tab in bottom navigation tab
Load state from Redux
Set Header Title
Expected
I want to set the following text to the Screen title
headerTitle set in Screen
Actual
I declared my BottomTabNavigator as following:
import FilterScreen from '../screens/FilterScreen';
import ItemsListScreen2 from '../screens/ItemsListScreen copy';
const BottomTab = createBottomTabNavigator();
const INITIAL_ROUTE_NAME = 'List';
export default function BottomTabNavigator({ navigation, route }) {
// this code works
navigation.setOptions({ headerTitle: 'header Set in Navigator' });
const [isFilterVisible, toggleFilter] = useState(false);
return (
<>
<BottomTab.Navigator
initialRouteName={INITIAL_ROUTE_NAME}
tabBarOptions={{
inactiveBackgroundColor: '#42a5f5',
inactiveTintColor: '#ffffff'
}}
>
<BottomTab.Screen
name="List"
component={ItemsListScreen}
options={{
title: 'List',
tabBarIcon: ({ focused }) => <TabBarIcon focused={focused} name="md-calendar" iconType="ion" />,
}}
/>
<BottomTab.Screen
name="List2"
headerMode="screen"
component={ItemsListScreen2}
screen
options={{
title: 'title List2',
headerTitle: 'my header title',
tabBarIcon: ({ focused }) => <TabBarIcon focused={focused} name="md-calendar" iconType="ion" />,
}}
/>
....
</BottomTab.Navigator>
</>
);
}
In the above component, I could set the Screen Title by using navigation.setOptions and it works.
So, I tried to do the same operation to set the headerTitle in List2 Screen. But it doesn't have any effect to the screen header. It only changes the label of the tab to "this is a title".
import { StyleSheet, View, Text } from 'react-native';
const ItemsListScreen2 = ({ navigation }) => {
navigation.setOptions({
headerTitle: `headerTitle set in Screen`, // <-- no effect at the screen level
title: "this is a title"
});
return (
<View>
<Text>Screen 2</Text>
</View>
);
}
I tried to do console.log to navigation object passed to the component and I can see it here:
Could you please help me to sort out this issue and suggest me how I could set the title from the screen?
Do I need to create a new function (Eg. getHeaderTitle), load the Redux state and set the title due to the route within BottomTabNavigation component? I want to keep that component clean and I want to load the Redux States only when the user navigates to the screen. I'd like to keep the screen specific codes in its own related screen.

React Native navigating between two screens back & forth

I have Home screen where we search for an ItemCode, after we search ItemCode it will get details from API & screen will navigate to Screen 1(Item Details Screen) & from Screen 1 we have a search for ItemNutrients and after search it will get Nutrients & Navigate to Screen 2 (Nutrients Screen)
In other words
Home -> Screen 1 (Item Details Screen) -> Screen 2 (Nutrients Screen)
After getting the necessary details from API from search for Item Details & Nutrients, User can navigate between Item Details & Nutrients back and forth..
I could navigate from Screen 1 (item Details) to Screen2 (Nutrients Screen) and swipe back to Screen 1 (item Details) but how could I swipe forward to look at Screen 2 (Nutrients Screen) again without searching for nutrients in Screen 1 as I already have the data from API for Nutrients.
Any ideas on how to implement this ? this is basically navigate to Screen 1 to Screen 2 on search from Screen 1 or if Screen 2 search is already done and we have data so swipe forward should navigate to Screen 2, I can have a button to navigate to Screen 2, the button on Screen 1 conditinally appears only when we have API data for Nutrients Screen and it will navigate to Screen 2 on that button click, but i need swipe functionality as well to navigate forward
I have Home, Screen 1, Screen 2 in stackNavigator, is it alright or i could use any other navigators to achieve this.
I have researched how to do this and can't find it in the docs anywhere. Please let me know if I am missing it somewhere. Thanks!
Nutrition Context:
import React, { createContext, useState, useEffect } from "react";
export const NutriSyncContext = createContext();
const DEFAULT_IS_NUTRI_SCANNED = false;
export const NutriSyncContextProvider = ({ children }) => {
const [isNutritionScanned, setisNutritionScanned] = useState(
DEFAULT_IS_NUTRI_SCANNED
);
// When we first mount the app we want to get the "latest" conversion data
useEffect(() => {
setisNutritionScanned(DEFAULT_IS_NUTRI_SCANNED);
}, []);
const nutrionScanClick = ({ navigation }) => {
setisNutritionScanned(true);
//navigation.navigate("NutritionFacts");
};
const contextValue = {
isNutritionScanned,
setisNutritionScanned,
nutrionScanClick,
};
return (
<NutriSyncContext.Provider value={contextValue}>
{children}
</NutriSyncContext.Provider>
);
};
inside navigation.js
const tabPNMS = createMaterialTopTabNavigator();
function tabPNMSStackScreen() {
const { isNutritionScanned } = useContext(NutriSyncContext);
console.log(isNutritionScanned);
return (
<tabPNMS.Navigator initialRouteName="PNMSNutrition" tabBar={() => null}>
<tabPNMS.Screen name="PNMSNutrition" component={PNMSNutrition} />
{isNutritionScanned ? (
<tabPNMS.Screen name="NutritionFacts" component={NutritionFacts} />
) : null}
</tabPNMS.Navigator>
);
}
and In stack Navigation:
const SearchStack = createStackNavigator();
const SearchStackScreen = () => (
<NutriSyncContextProvider>
<SearchStack.Navigator
initialRouteName="Search"
screenOptions={{
gestureEnabled: false,
headerStyle: {
backgroundColor: "#101010",
},
headerTitleStyle: {
fontWeight: "bold",
},
headerTintColor: "#ffd700",
headerBackTitleVisible: false, //this will hide header back title
}}
headerMode="float"
>
<SearchStack.Screen
name="Search"
component={Search}
options={({ navigation }) => ({
title: "Search",
headerTitleAlign: "center",
headerLeft: () => (
<Entypo
name="menu"
size={24}
color="green"
onPress={() => navigation.dispatch(DrawerActions.toggleDrawer())}
/>
),
})}
/>
<SearchStack.Screen
name="NutriTabs"
options={() => ({
title: "PNMS",
headerTitleAlign: "center",
})}
component={tabPNMSStackScreen}
/>
</SearchStack.Navigator>
</NutriSyncContextProvider>
);
I have NutriSyncContextProvider around the stack, but when I click the button on my page it will call nutrionScanClick method in context class it works just fine but when i do navigation.navigate to the NutritionFacts tab that is enabled i get error saying NutritionFacts tab doesn't exist
I got it working below is the code:
const nutrionScanClick = () => {
if (!isNutritionScanned) setisNutritionScanned(true);
else {
//Make a API call here for new data for the new scanned one's and then navigate
navigation.navigate("NutritionFacts");
}
};
useEffect(() => {
if (isNutritionScanned) {
navigation.navigate("NutritionFacts");
}
}, [isNutritionScanned]);
There's no swipe back n forth between screens in a stack navigator. You need to use a tab navigator to be able to keep multiple screens like that.
https://reactnavigation.org/docs/material-top-tab-navigator
import { createMaterialTopTabNavigator } from '#react-navigation/material-top-tabs';
const Tab = createMaterialTopTabNavigator();
function MyTabs() {
return (
<Tab.Navigator tabBar={() => null}>
<Tab.Screen name="ItemDetails" component={ItemDetails} />
{showNutrients ? <Tab.Screen name="Nutrients" component={Nutrients} /> : null}
</Tab.Navigator>
);
}

React Navigation (V2 / V3): How to access navigation.state.index in navigationOptions on screen

I'm building a React Native app and for my navigation I use React Navigation (V3). For my UI elements I use React Native Elements.
I have a screen which I reuse in multiple navigators. Sometimes it is at the root of a stack, and sometimes it's pushed on with a stack. I want to programmatically add a headerLeft element to this screen navigationOptions based on its position in the stack, because I either want the default back button to show, or a burger menu icon to show, if the screen is at the root of the stack (because the stack is nested in a drawer).
So I tried the following:
export class ScannerScreen extends Component {
static navigationOptions = ({ navigation }) => ({
headerLeft:
navigation.state.index > 0 ? (
undefined
) : (
<Icon type="ionicon" name="ios-menu" onPress={navigation.toggleDrawer} />
),
title: strings.scannerTitle
});
// ...
The problem is this doesn't work as navigation.state.index is undefined here. How would one do this with React navigation?
Edit:
As per request I added a screenshot of console.log(navigation) (via <Icon />s onPress.)
I found a hacky solution, which is okay but I also dislike it a bit:
const stackConfig: StackNavigatorConfig = {
cardStyle: styles.card,
navigationOptions: {
headerBackTitleStyle: styles.headerBackTitle,
headerStyle: styles.header,
headerTintColor: "black"
}
};
const HomeStack = createStackNavigator(
{ HomeScreen, RestaurantDetailScreen, MenuScreen, ScannerScreen },
{
...stackConfig,
initialRouteName: "HomeScreen",
navigationOptions: ({ navigation }) => ({
headerLeft:
parseFloat(navigation.state.key.slice(-1)) > 1 ? (
undefined
) : (
<Icon
containerStyle={styles.leftIcon}
type="ionicon"
name={getIconName("menu")}
onPress={navigation.toggleDrawer}
/>
),
...stackConfig.navigationOptions
})
}
);
This way it automatically sets it for the whole stack. You can also do this in the respective screen individual navigation options. But this only works, since the automatically assigned key for the screen contains it's index.

The best way to create a scrollable tab in the middle of the screen?

The mobile app of Twitter has a scrollable tab in the middle of the screen when you are on your profile. The top half of the screen displaying your profile info etc doesn't change when you click on the scrollable tabs mid screen : "Tweets & replies", "Media" etc. I am wondering how one would create this? Having half the screen stay the same and then having tabs which change mid screen... At the moment I have react navigation tabs as my main navigation - so on one of these tabs (the profile tab) I want to create the same concept as the picture..
Late answer but (for anyone else and future reference), react-navigation uses this package, react-native-tab-view: https://github.com/react-native-community/react-native-tab-view
for their tabs.
You can nest this within a screen, just like you desire (the previous answer only addresses the navigator inside navigator and that isn't what you want).
Here is an example (not exactly like you want, but you get the idea that you can. so instead of a background image, swap it out and use a view or scrollview accordingly to create that layout):
https://snack.expo.io/#satya164/collapsible-header-with-tabview
cheers :)
EDIT: i just found a way with just using react-navigation after all:
https://snack.expo.io/#mattx/collapsible-header-tabs
check it out
and another library: https://github.com/benevbright/react-navigation-collapsible
I don't know if you've figured it out yet, but you can nest the TabNavigator inside a StackNavigator. That way, you can have a scrollable Tab.
class ProfileMenu extends React.Component{
render() {
return(
//whatever you wanted at the top
)
}
}
const TabNaviga = createMaterialTopTabNavigator({
Tweets: {screen: TweetScreen,},
Replies: {screen: RepliesScreen,},
})
const YNavigator = createStackNavigator ({
Home:{screen: TabNaviga,
navigationOptions: ({navigation}) => ({
header: <ProfileMenu navigation= {navigation} />,
})
}
})
export default YNavigator
I found this tutorial and followed it,
EDIT: it seems there's a new library out that supports it https://github.com/PedroBern/react-native-collapsible-tab-view
https://medium.com/#linjunghsuan/implementing-a-collapsible-header-with-react-native-tab-view-24f15a685e07
I also wrote a bit of an explaination if you are interested.
create a ScrollY with useRef and .current at the end
create a handleScroll function which returns an event like so -
const handleScroll = Animated.event(
[{ nativeEvent: { contentOffset: { y: scrollY } } }],
{ useNativeDriver: true }
);
Pass it down in props to the wanted component
<TabNavigator handleScroll={handleScroll} scrollY={scrollY} />
And also the scrollY so you can use the value in the Child component aswell
pass it farther down the line to actual events like and call handleScroll in the Child Child component onScroll prop. like so
<Animated.FlatList
...
onScroll={handleScroll}
/>
And now you can use the ScrollY value wherever you want.
what it does is checking if the current route is the one we check, it then caluclates the offset and scrollToOffset function of flatlist using the flatlist refs we got from here
return (
<Pictures
handleScroll={handleScroll}
onMomentumScrollBegin={onMomentumScrollBegin}
onScrollEndDrag={onScrollEndDrag}
onMomentumScrollEnd={onMomentumScrollEnd}
onGetRef={ref => {
if (ref) {
const found = listRefArr.current.find(e => e.key === route.key);
if (!found) {
listRefArr.current.push({ key: route.key, value: ref });
}
}
}}
/>
);
the onGetRef is connected to the FlatList ref
return (
<AnimatedFlatList
ref={onGetRef}
scrollToOverflowEnabled
onMomentumScrollBegin={onMomentumScrollBegin}
onScrollEndDrag={onScrollEndDrag}
onMomentumScrollEnd={onMomentumScrollEnd}
onScroll={handleScroll}
scrollEventThrottle={16}
contentContainerStyle={{
paddingTop: HeaderHeight + TabBarHeight,
paddingHorizontal: 10,
minHeight: windowHeight - TabBarHeight
}}
data={data}
renderItem={({ item }) => {
return <Comment data={item} />;
}}
keyExtractor={({ commentId }): any => {
return commentId.toString();
}}
/>
);
then we have these three functions which we send the flatlist as well
const onMomentumScrollBegin = () => {
isListGliding.current = true;
};
const onMomentumScrollEnd = () => {
isListGliding.current = false;
syncScrollOffset();
};
const onScrollEndDrag = () => {
syncScrollOffset();
};
and last but not least we still need to animate the TabBar so when the header is 500 height his is 0 when the header is 450 in the y the tabbar should be 50, we do that by getting the scrollY in the props and use it to interpolate.
const renderTabBar = (props: any) => {
return (
<Animated.View
style={{
top: 0,
zIndex: 1,
position: "absolute",
transform: [{ translateY: tabViewHeight }],
width: "100%"
}}
>
<TabBar ... />
</Animated.View>
);
};

Can't display leftComponentIcon in NavigationBar

I add react-native-navigation-bar to my project. I want to display the left icon as a sidebar icon. I try this code but nothing is displayed :
<NavigationBar
title={'New Tech'}
height={80}
titleColor={'#fff'}
leftComponent={<Icon name="rocket"/> }
/>
ps: am installing react-native-vector-icons and import it by :
import Icon from 'react-native-vector-icons/FontAwesome';
Any help isappreciated.
It depends on your Navigation Version, but it is either called left or headerLeft (newer versions is the later):
static navigationOptions = ({ navigation }) => ({
title: `whatever`,
headerLeft: <Button title="" onPress={() => {/*do something*/}} />,
});