How to integrate React Navigation Back Button with a WebView - reactjs

I tried following this tutorial https://www.reactnativeschool.com/integrating-react-navigation-back-button-with-a-webview.
Here is my Index.js/App.js code. Any guidance mainly HOW TO ADD adding natigationOptions to Stack.Screen.
import React, { useEffect } from "react";
import { StyleSheet, Text, View } from "react-native";
import { NavigationContainer } from "#react-navigation/native";
import {
createStackNavigator,
HeaderBackButton, // new import
} from "#react-navigation/stack";
import Home from "./screens/Home";
import Details from "./screens/Details";
import Icon from "react-native-vector-icons/FontAwesome";
const Stack = createStackNavigator();
const App = () => {
return (
<NavigationContainer>
<Stack.Navigator
screenOptions={{
headerShown: true,
}}
>
<Stack.Screen name="Home" component={Tabs} />
<Stack.Screen
name="Details"
component={Details}
options={({ navigation }) => ({
headerTitle: "Details",
headerLeft: (props) => {
const headerLeftInfo = navigation.getParam("headerLeftInfo");
if (headerLeftInfo) {
return (
<HeaderBackButton
{...props}
title={headerLeftInfo.title}
onPress={headerLeftInfo.onPress}
/>
);
}
return <HeaderBackButton {...props} />;
},
})}
/>
</Stack.Navigator>
</NavigationContainer>
);
};
export default App;
const styles = StyleSheet.create({});
How to add navigationOptions like the code below:
const App = createStackNavigator({
Index: {
screen: Index,
navigationOptions: {
headerTitle: 'Index',
},
},
Details: {
screen: Details,
navigationOptions: ({ navigation }) => ({
headerTitle: 'Details',
}),
},
});

Related

this.props.fetch user is not a function

import React, { Component } from "react";
import { createBottomTabNavigator } from "#react-navigation/bottom-tabs";
import MaterialCommunityIcons from "react-native-vector-icons/MaterialCommunityIcons";
import { connect } from "react-redux";
import { bindActionCreators } from "redux";
import { NavigationContainer, TabActions } from "#react-navigation/native";
import { fetchUser } from "../redux/actions";
const Tab = createBottomTabNavigator();
import FeedScreen from "./main/Feed";
import ProfileScreen from "./main/Profile";
import AddScreen from "./main/Add";
export class Main extends Component {
componentDidMount() {
this.props.fetchUser();
}
render() {
return (
<Tab.Navigator>
<Tab.Screen
name="Feed"
component={FeedScreen}
options={{
tabBarIcon: ({ color, size }) => (
<MaterialCommunityIcons name="home" color={color} size={26} />
),
}}
/>
<Tab.Screen
name="Add"
component={AddScreen}
options={{
tabBarIcon: ({ color, size }) => (
<MaterialCommunityIcons name="plus-box" color={color} size={26} />
),
}}
/>
<Tab.Screen
name="Profile"
component={ProfileScreen}
options={{
tabBarIcon: ({ color, size }) => (
<MaterialCommunityIcons
name="account-circle"
color={color}
size={26}
/>
),
}}
/>
</Tab.Navigator>
);
}
}
const mapStateToProps = (store) => ({
currentUser: store.userState.currentUser,
});
const mapDispatchProps = (dispatch) => bindActionCreators({});
export default connect(mapStateToProps, mapDispatchProps)(Main);
this is my code and I am very confused it says that its imported but fetchUser is not being read.
im trying to export it from this file
import { USER_STATE_CHANGE } from "../constants/index";
import firebase from "firebase/compat/app";
import "firebase/compat/auth";
import "firebase/compat/firestore";
export function fetchUser() {
return (dispatch) => {
firebase
.firestore()
.collection("user")
.doc(firebase.auth().currentUser.uid)
.get()
.then((snapshot) => {
if (snapshot.exists) {
dispatch({ type: USER_STATE_CHANGE, currentUser: snapshot.data() });
} else {
console.log("does not exist");
}
});
};
}
if someone can please help. I have been stuck on this for days
I have tried searching forums and have found nothing. I went through the course again and still nothing I am currently working on this to meet a deadline for an important project and I have not been able to come to a solution. I have all the necessary packages installed, but still no dice. The source code did not help much either, so if anyone can hep it would be much appreciated.
Try having your mapDispatchToProps like this:
const mapDispatchProps = (dispatch) => ({ fetchUser: () => fetchUser()(dispatch) });
Here's the full file with the change applied:
import React, { Component } from "react";
import { createBottomTabNavigator } from "#react-navigation/bottom-tabs";
import MaterialCommunityIcons from "react-native-vector-icons/MaterialCommunityIcons";
import { connect } from "react-redux";
import { bindActionCreators } from "redux";
import { NavigationContainer, TabActions } from "#react-navigation/native";
import { fetchUser } from "../redux/actions";
const Tab = createBottomTabNavigator();
import FeedScreen from "./main/Feed";
import ProfileScreen from "./main/Profile";
import AddScreen from "./main/Add";
export class Main extends Component {
componentDidMount() {
this.props.fetchUser();
}
render() {
return (
<Tab.Navigator>
<Tab.Screen
name="Feed"
component={FeedScreen}
options={{
tabBarIcon: ({ color, size }) => (
<MaterialCommunityIcons name="home" color={color} size={26} />
),
}}
/>
<Tab.Screen
name="Add"
component={AddScreen}
options={{
tabBarIcon: ({ color, size }) => (
<MaterialCommunityIcons name="plus-box" color={color} size={26} />
),
}}
/>
<Tab.Screen
name="Profile"
component={ProfileScreen}
options={{
tabBarIcon: ({ color, size }) => (
<MaterialCommunityIcons
name="account-circle"
color={color}
size={26}
/>
),
}}
/>
</Tab.Navigator>
);
}
}
const mapStateToProps = (store) => ({
currentUser: store.userState.currentUser,
});
const mapDispatchProps = (dispatch) => ({ fetchUser: () => fetchUser()(dispatch) });
export default connect(mapStateToProps, mapDispatchProps)(Main);

React Native Custom Navbar issue

Hey so I am having a little brain meltdown,
I cannot for the life of me figure out how I should do this.
What I am trying to do is have my custom navbar not show on the login screen but show anywhere else.
I explained in this short video: https://cdn.discordapp.com/attachments/643261623519150090/904146526778056805/Desktop_2021.10.30_-_16.10.14.04.mp4
This is my code:
[ root ./ ] App.js
import LoginScreen from './views/login'
import HomeScreen from './views/home'
import ProfileScreen from './views/profile'
import CustomNav from './views/nav/index'
const Stack = createStackNavigator();
export default function App() {
return (
<View style={styles.Test} >
<NavigationContainer theme={DarkTheme} linking={{
config: {
screens: {
Login: "/",
Home: "/home",
Profile: "/Profile/:Username"
}
},
}}>
<Stack.Navigator
screenOptions={{
headerShown: false
}}>
<Stack.Screen name="Login" component={LoginScreen} />
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Profile" component={ProfileScreen} />
</Stack.Navigator>
<CustomNav Nav="Mobile" />
</NavigationContainer>
</View>
);
}
[ ./nav/index] index.js
import React, {useEffect, useState} from 'react';
import {} from 'react-native';
import MobileNav from './mobile'
import WebsiteNav from './website'
const CustomNav = () => {
const [viewing, setViewing] = useState('login');
useEffect(() => {
if(viewing === 'login'){
console.log("Viewing Login")
}else{
console.log("Not Login")
}
}, []);
return(
null
);
}
export default CustomNav;

React Native call navigator inside child navigator screen

I have a main navigator in app.js
import React from 'react';
import LoginScreen from './src/screens/LoginScreen';
import HomeScreen from './src/screens/HomeScreen';
import { Image } from 'react-native';
import { createAppContainer } from 'react-navigation';
import { createStackNavigator } from 'react-navigation-stack';
import { Constans } from './src/constants/Constant';
import SignupScreen from './src/screens/SignupScreen';
import { EventScreen } from './src/screens/EventScreen';
const navigator = createStackNavigator(
{
Login: {
screen: LoginScreen,
navigationOptions: {
headerShown: false
}
},
SignUp: {
screen: SignupScreen,
navigationOptions: {
headerShown: false
}
},
Home: {
screen: HomeScreen,
navigationOptions: {
headerLeft:()=>false,
headerTitle:()=> (
<Image style={{width:35,height:35}} source={Constans.logoImage}/>
)
}
},
Event:{
screen:EventScreen,
navigationOptions: {
headerLeft:()=>false,
headerTitle:()=> (
<Image style={{width:35,height:35}} source={Constans.logoImage}/>
)
}
}
},
{
initialRouteName: 'Home'
}
);
const AppContainer = createAppContainer(navigator);
export default class App extends React.Component {
render() {
return <AppContainer />;
}
}
and when I go HomeScreen I have also menu navigator.
import React from 'react';
import Icon from 'react-native-vector-icons/FontAwesome';
import { NavigationContainer } from '#react-navigation/native';
import { createBottomTabNavigator } from '#react-navigation/bottom-tabs';
import { TabEventsScreen } from './TabEventsScreen';
import { TabForYouScreen } from './TabForYouScreen';
import { TabProfileScreen } from './TabProfileScreen';
const Tab = createBottomTabNavigator();
export default function HomeScreen() {
return (
<NavigationContainer>
<Tab.Navigator
screenOptions=
{
({ route }) =>
(
{
tabBarIcon: ({ focused, color }) =>
{
return <Icon
name={route.name === 'Explore' ? "globe" : route.name === 'Profile' ? 'user' : route.name === 'Wallet' ? 'money' :'star'}
size={focused ? 32: 24}
color={color} />;
}
}
)
}
tabBarOptions=
{
{
activeTintColor: '#1F7A8C',
inactiveTintColor: 'gray',
}
}>
<Tab.Screen name="Explore" component={TabEventsScreen} />
<Tab.Screen name="For You" component={TabForYouScreen} />
<Tab.Screen name="Wallet" component={TabProfileScreen} />
<Tab.Screen name="Profile" component={TabProfileScreen} />
</Tab.Navigator>
</NavigationContainer>
);
}
in one tab screen I am trying to navigate Event screen of main navigator. How can I do it?
import React, { useState, useEffect } from 'react';
import { Constans } from './../constants/Constant';
import { View, ImageBackground, Text, ScrollView } from 'react-native';
import { TabEventsScreenStyle } from './../styles/TabEventsScreenStyle';
import { IncomingEvents } from './../services/TabEventsService';
import { EventCard } from './../components/Eventcard'
export function TabEventsScreen({navigation}) {
const [events, setEvents] = useState([]);
const [page, setPage] = useState(1);
_getEvents = () => {
return IncomingEvents(1, page, 20);
}
useEffect(() => {
setEvents(_getEvents().Events);
});
_OnButtonPress=(key)=>{
console.log(navigation)
navigation.navigate('Event',{itemId:key});
}
return (
<ScrollView>
<ImageBackground source={Constans.homeBackGroundImage} style={TabEventsScreenStyle.backgroundImage} >
<Text style={TabEventsScreenStyle.title}>Incoming Events</Text>
<View>
{
events.map(el =>
<Text style={TabEventsScreenStyle.text} onPress={()=> this._OnButtonPress(el.key)}>
<EventCard key={el.key} data={el}></EventCard>
</Text>
)
}
</View>
</ImageBackground>
</ScrollView>
);
}
my error is
The action 'NAVIGATE' with payload
{"name":"Event","params":{"itemId":1}} was not handled by any
navigator.
Do you have a screen named 'Event'?
If you're trying to navigate to a screen in a nested navigator, see
https://reactnavigation.org/docs/nesting-navigators#navigating-to-a-screen-in-a-nested-navigator.
I also tried
const parent_nav=navigation.dangerouslyGetParent();
console.log(navigation);
parent_nav.navigate('Event',{itemId:key});
but parent_nav is undefined
Thanks in advance
You can navigate as if all screens were at the same level.
Navigation actions are handled by current navigator and bubble up if couldn't be handled
For example, if you're calling navigation.goBack() in a nested screen, it'll only go back in the parent navigator if you're already on the first screen of the navigator. Other actions such as navigate work similarly, i.e. navigation will happen in the nested navigator and if the nested navigator couldn't handle it, then the parent navigator will try to handle it. In the above example, when calling navigate('Messages'), inside Feed screen, the nested tab navigator will handle it, but if you call navigate('Settings'), the parent stack navigator will handle it.
The source.
When you navigate, it will try to find your screen in the current navigation and if it failed, then will try with the parent until the root navigation.
Wrap your AppContainer with NavigationContainer instead of the TabNavigator, so that both the StackNavigator and TabNavigator use the same navigation prop
export default class App extends React.Component {
render() {
return(
<NavigationContainer>
<AppContainer />
<NavigationContainer/>
)}
}
And remove NavigationContainer from the TabNavigator
Here is how I nested Stack and Tab Navigator
import React from 'react';
import {Dimensions, StatusBar, StyleSheet} from 'react-native';
import {createMaterialBottomTabNavigator} from '#react-navigation/material-bottom-tabs';
import {NavigationContainer} from '#react-navigation/native';
import {createStackNavigator} from '#react-navigation/stack';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
import AScreen from './AScreen';
import BScreen from './BScreen';
import CScreen from './CScreen';
import DScreen from './DScreen';
import A from './a';
import B from './B';
import C from './C';
import D from './D';
const Tab = createMaterialBottomTabNavigator();
const Stack = createStackNavigator();
const BottomTab = () => {
return (
<Tab.Navigator
activeColor="#ff6362"
backBehavior="initialRoute"
barStyle={{backgroundColor: '#000000'}}>
<Tab.Screen
name="A"
component={AScreen}
options={{
tabBarLabel: 'A',
tabBarIcon: ({color}) => (
<MaterialCommunityIcons name="book-open" color={color} size={26} />
),
}}
/>
<Tab.Screen
name="B"
component={BScreen}
options={{
tabBarLabel: 'B',
tabBarIcon: ({color}) => (
<MaterialIcons name="people" color={color} size={26} />
),
}}
/>
<Tab.Screen
name="C"
component={CScreen}
options={{
tabBarLabel: 'C',
tabBarIcon: ({color}) => (
<MaterialIcons name="people" color={color} size={26} />
),
}}
/>
<Tab.Screen
name="D"
component={DScreen}
options={{
tabBarLabel: 'D',
tabBarIcon: ({color}) => (
<MaterialIcons name="person-pin" color={color} size={26} />
),
}}
/>
</Tab.Navigator>
);
};
export default function AdminScreen(props) {
return (
<NavigationContainer>
<StatusBar hidden />
<Stack.Navigator headerMode="none">
<Stack.Screen name="BottomTab" component={BottomTab} />
<Stack.Screen name="A" component={A} />
<Stack.Screen name="B" component={B} />
<Stack.Screen name="C" component={C} />
<Stack.Screen name="D" component={D} />
</Stack.Navigator>
</NavigationContainer>
);
}

Testing: react-native (expo) take snapshot of a Screen component inside of react-navigation

My test attempt currently looks like this:
import React from "react"
import { NavigationContainer } from "#react-navigation/native"
import { createStackNavigator } from "#react-navigation/stack"
import { render, waitFor } from "#testing-library/react-native"
import { Provider } from "react-redux"
import { createStore } from "redux"
import rootReducer from "src/store/reducers/root-reducer"
import ProfileScreen from "src/screens/profile"
const store = createStore(rootReducer)
test("renders correctly", async () => {
const Screen = () => {
// const navigationRef = React.useRef(null) // cause "Invalid hook call. Hooks can only be called inside .."
const Stack = createStackNavigator()
return (
<Provider store={store}>
<NavigationContainer
// ref={navigationRef} // cause "Invalid hook call. Hooks can only be called inside .."
>
<Stack.Navigator>
<Stack.Screen name="Profile" component={ProfileScreen} />
</Stack.Navigator>
</NavigationContainer>
</Provider>
)
}
const tree = render(<Screen />)
await waitFor(async () => expect(tree.toJSON()).toMatchSnapshot())
})
But it keeps throwing this:
...
console.error
The above error occurred in the <ForwardRef(NavigationContainer)> component:
in Provider (created by Screen)
in Screen
Consider adding an error boundary to your tree to customize error handling behavior.
Visit <fb link> to learn more about error boundaries
...
I can't find any useful docs/examples on the react-navigation site regrading testing a screen. (I also googled a lot around)
I can try to boil down the code to something like this:
App.json
...
export default function App() {
...
return (
<Provider store={store}>
<Navigation />
</Provider>
)
}
Navigation.js
import React from "react"
import { NavigationContainer, DarkTheme } from "#react-navigation/native"
import { createStackNavigator } from "#react-navigation/stack"
import BottomTabNavigator from "./BottomTabNavigator"
export default function Navigation() {
const navigationRef = React.useRef(null)
return (
<NavigationContainer
theme={DarkTheme}
ref={navigationRef} // https://reactnavigation.org/docs/navigation-container/#ref
>
<RootNavigator />
</NavigationContainer>
)
}
const Stack = createStackNavigator()
function RootNavigator() {
return (
<Stack.Navigator screenOptions={{ headerShown: false }}>
<Stack.Screen name="Root" component={BottomTabNavigator} />
...
</Stack.Navigator>
)
}
BottomTabNavigator
import React from "react"
import { Linking, TouchableWithoutFeedback, View } from "react-native"
import { createBottomTabNavigator } from "#react-navigation/bottom-tabs"
import { createStackNavigator } from "#react-navigation/stack"
import { Feather } from "#expo/vector-icons"
import Profile from "src/screens/profile"
import SignIn from "src/screens/sign-in"
const BottomTab = createBottomTabNavigator()
export default function BottomTabNavigator() {
const TabIcon = ({ name, color }) => <Feather name={name} color={color} size={20} />
return (
<BottomTab.Navigator
tabBarOptions={{
activeTintColor: Colors.defaultWhite,
style,
}}
>
<BottomTab.Screen
name="Profile"
component={ProfileNavigator}
options={{
tabBarIcon: ({ color }) => <TabIcon name="user" color={color} />,
title: I18n.t("components.tab_navigator.profile"),
}}
/>
)
}
const ProfileStack = createStackNavigator()
function ProfileNavigator() {
return (
<ProfileStack.Navigator>
<ProfileStack.Screen name="Profile" component={Profile} options={{ headerShown: false }} />
<ProfileStack.Screen name="SignIn" component={SignIn} options={{ headerShown: false }} />
</ProfileStack.Navigator>
)
}

Override Header Title React Navigation V5

I have created a nested tab navigator in my stack navigator as follows:
const Stack = createStackNavigator()
const Tab = createBottomTabNavigator();
function TabNav () {
return (
<Tab.Navigator>
<Tab.Screen name="Home" component={HomeScreen}></Tab.Screen>
<Tab.Screen name="Category" component={CategoryScreen}></Tab.Screen>
<Tab.Screen name="Settings" component={SettingsScreen}></Tab.Screen>
</Tab.Navigator>
)
}
export default function App() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="Tab" component={TabNav}></Stack.Screen>
<Stack.Screen name="Category" component={CategoryScreen}>
</Stack.Screen>
<Stack.Screen
name="SpecialBeerScreen" component= {SpecialBeerScreen}>
</Stack.Screen>
</Stack.Navigator>
</NavigationContainer>
)
}
However, the header now looks like Tab for each of the screens. How do I override this Header for each of the screens with a custom text, such as Home, Category etc.
Update: This is still valid in react-navigation v6
Use getFocusedRouteNameFromRoute as outlined in the react-navigation docs in the section Setting parent screen options based on child navigator's state to access information of screens in nested navigators.
Change this line
<Stack.Screen name="Tab" component={TabNav}></Stack.Screen>
to the following (adding the options prop):
<Stack.Screen
name="Tab"
component={TabNav}
options={({ route }) => {
const routeName = getFocusedRouteNameFromRoute(route) ?? 'Home';
switch (routeName) {
case 'Category': {
return {
headerTitle: 'Category',
};
}
case 'Settings': {
return {
headerTitle: 'Settings',
};
}
case 'Home':
default: {
return {
headerTitle: 'Home',
};
}
}
}}
/>
You also have to import getFocusedRouteNameFromRoute, so also add:
import { getFocusedRouteNameFromRoute } from '#react-navigation/native';
If I understand your problem correctly, you wanted to change the stack title when the tab is changed. In this case, you may use React Context to control it.
(I also put this code in snack https://snack.expo.io/#gie3d/change-header-from-tab)
edit: 1, I separated it into 3 files and assume that it's all in the same directory.
HomeTitleContext.js
export const HomeTitleContext = React.createContext({
title: 'default title',
setTitle: () => {},
});
App.js
import { HomeTitleContext } from './HomeTitleContext';
export default function App() {
const [title, setTitle] = useState('default title');
return (
<HomeTitleContext.Provider
value={{
title,
setTitle,
}}
>
<HomeTitleContext.Consumer>
{(ctx) => (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
name="Tab"
component={TabNav}
options={{ title: ctx.title }} // The title will be dynamically changed here
/>
<Stack.Screen
name="Category"
component={OtherScreen}
></Stack.Screen>
<Stack.Screen
name="SpecialBeerScreen"
component={OtherScreen}
></Stack.Screen>
</Stack.Navigator>
</NavigationContainer>
)}
</HomeTitleContext.Consumer>
</HomeTitleContext.Provider>
);
}
In your component, for example: HomeScreen, you set up a useFocusEffect and change the title from setTitle which you'll get from the context
HomeScreen.js
import React, { useContext } from 'react';
import { useFocusEffect } from '#react-navigation/native';
import { View, Text } from 'react-native';
import { HomeTitleContext } from './HomeTitleContext';
const HomeScreen = ({ navigation }) => {
const { setTitle } = useContext(HomeTitleContext);
useFocusEffect(() => {
setTitle('this is home');
});
return (
<View>
<Text>Home</Text>
</View>
);
};

Resources