How to navigate back to parent page from inside an custom component in React Native - react-native

Greeting everyone,
I'm writing a React Native application and have stumbled into something interesting.
This is the simplified version of my code:
//import statements
...
const Profile = (props) => {
return(
<Text>Hey {props.name}!</Text>
<TouchableOpacity onPress={() => navigation.goBack();}>Go back</TouchableOpacity>
);}
export function ScreenOne({ navigation }) {
<Profile name="HUFF">
}
export function ScreenTwo({ navigation }) {
//blah blah blah
}
export default function App(){
//rest of the code
...
}
So basically, I'm trying to use react-navigation to navigate between screens of my App. But the button inside of my Profile component doesn't work.
Consider that I navigate from ScreenTwo to ScreenOne
Thanks in Advance.

React navigation has useNavigation hook
import { useNavigation } from '#react-navigation/native'
// if you are not using react-navigation 5 or 6
// then there is package https://github.com/react-navigation/hooks
const Profile = (props) => {
const navigation = useNavigation();
return(
<Text>Hey {props.name}!</Text>
<TouchableOpacity onPress={() => navigation.goBack()}>Go back</TouchableOpacity>
);
}

For this, you might have to specify navigation via props on the Profile JSX tag, so it can access the parent's navigation inside the component. E.g.:
//import statements
...
const Profile = (props) => {
return(
<Text>Hey {props.name}!</Text>
<TouchableOpacity onPress={() => props.navigation.goBack()}>Go back</TouchableOpacity>
);
}
function ScreenOne({ navigation }) {
<Profile name="HUFF" navigation={navigation}>
}
function ScreenTwo({ navigation }) {
//blah blah blah
}
export default function App(){
//rest of the code
...
}

Related

Navigate from a function call React native

Hi guys I am having trouble finding a solution on my problem because I want to navigate to another page but here's the problem
function NewOrderPopUp({id, services, name, rating, accepted, destinationPlaceName, userPlaceName, driverName, driverContactNumber, driverRating, driverTrackingNumber})
{
async function toggleAcceptBooking()
{
await firestore()
.collection('userBookingRequest')
.doc(id)
.update({
accepted : !accepted,
driverName: 'Sample Driver',
driverContactNumber: '09123456789',
driverRating: '4.9',
driverPlateNumber: 'NFT-2020',
driverTrackingNumber: GenerateTrackingNumber(),
})
CheckIfBookingAccepted();
}
}
return (
<View>....</View>
);
export default NewOrderPopUp;
And I am calling the NewOrderPopUp Page in another file.
like this
import NewOrderPopUp from "../../components/NewOrderPopUp";
const HomeScreen = () => {
//... codes here
return (
<View>
<FlatList
horizontal
contentContainerStyle={{paddingHorizontal: 30}}
data={userBookingData}
keyExtractor={(item) => item.id}
renderItem={({item}) => <NewOrderPopUp {...item}/>} />
</View>
);
}
export default HomeScreen;
What I wanted is that if I click the toggleAcceptBooking it will nagivate to another page like
navigation.navigate('BookingPage');
Can someone enlighten me please . Thank you.
Do it by passing navigation down as a prop.
do the following steps.
handle navigation prop in HomeScreen
const HomeScreen = ({ navigation }) => {
}
pass navigation as a prop to NewOrderPopUp
<FlatList
...
renderItem={({item}) => <NewOrderPopUp navigation={navigation} {...item}/>} />
handle navigation prop in NewOrderPopUp and use it to navigate.
function NewOrderPopUp( {navigation, ...} ){
async function toggleAcceptBooking(){
await ...
navigation.navigate('BookingPage');
}
}

React native #5Navigation react testing library

I am new to React Native Testing Library. For my React native app I am using styled components and Typescript. I fetched the data and pass to my flatList. In Flat-list's render item I have created one global component where it display all the data which is wrap with one touchable container. When user will click the that touchable opacity it will go to single product details screen.
For testing the component I have created one mock container. And I wrap my touchable opacity component. I followed this article to create mocked navigator. I want to test the touchable opacity component and it navigate to the next screen. But I am getting error and it says:
The action 'NAVIGATE' with payload
{"name":"ProductDetails","params":{"product":{"__typename":"Product","id":"6414893391048","ean":"6414893391048","name":"T
shirt","brandName":"Denim","price":13.79 }} was not handled by any
navigator.
This my component
const navigation = useNavigation();
const onPress = () => {
trackProductView({
id: item.id ,
name: item.name,
});
navigation.navigate(Routes.ProductDetails, { product: item });
};
return (
<TouchableOpacity
accessible={true}
{...a11y({
role: 'menuitem',
label: item.name,
})}
onPress={onPress} // This is my onPress function
>
<ItemContainer>
<ProductTitleText ellipsizeMode={'tail'} numberOfLines={2}>
{item.name}
</ProductTitleText>
<QuantityModifierWrapper>
<QuantityModifier item={item!} />
</QuantityModifierWrapper>
</ItemContainer>
</TouchableOpacity>
);
This is my mocked container
import React from 'react';
import { NavigationContainer } from '#react-navigation/native';
import { createStackNavigator } from '#react-navigation/stack';
import 'react-native-gesture-handler';
import { MockedProvider } from '#apollo/client/testing';
type Props = {
screen: any;
params?: any;
};
const Stack = createStackNavigator();
const MockedNavigator = ({ screen, params = {} }: Props) => {
return (
<MockedProvider>
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
name='MockedScreen'
component={screen}
initialParams={params}
/>
</Stack.Navigator>
</NavigationContainer>
</MockedProvider>
);
};
export default MockedNavigator;
This is my mocked screen
import React from 'react';
import { View } from 'react-native';
type Props = {
children: React.ReactNode;
};
const MockedScreen = ({ children }: Props) => {
return <View>{children}</View>;
};
export default MockedScreen;
This is my test suite where I am getting failed test
import React from 'react';
import { fireEvent, render, cleanup } from 'skm/utils/testing_utils';
import Touchablecomponent from './Touchable';
import MockedNavigator from './MockNav';
import MockedScreen from './Mockscreen';
describe('<Touchablecomponent/> ', () => {
test("render with invalid data", async () => {
const screenName = 'ProductDetails';
const component = (
<MockedNavigator
screen={() => (
<MockedScreen>
<ProductItemSmall item={mockData} />
</MockedScreen>
)}
// params={{data: mockData }}
/>
);
const { getByA11yRole, debug, toJSON } = render(component);
const item = getByA11yRole('menuitem');
console.log(fireEvent.press(item));
});
})

useRef to control a component that is inside child component

I am developing react-native project.
I have created a custom component inside which I use a 3rd party library component. The 3rd party library component needs the useRef hook from my code so that I can control the library component.
import React, {useRef} from 'react';
...
const MyComponent = () => {
const ref = useRef();
return (
<View>
<Button
title="OPEN 3rd party component"
onPress={() => ref.current.open()}
/>
<LibraryComponent
ref={ref}
...
/>
</View>)
}
Now, I have a screen named MyScreen, I need to show MyComponent on MyScreen, and control that library component from MyScreen instead of MyComponent. So, I refactored MyComponent to this (I removed the Button and change ref to be a pass-in property):
const MyComponent = ({ref}) => {
return (
<View>
<LibraryComponent
ref={ref}
...
/>
</View>)
}
Then, in MyScreen :
import React, {useRef} from 'react';
import MyComponent from '../components/MyComponent';
export const MyScreen = () => {
const screenRef = useRef();
...
return (
<View style={styles.container}>
<Button onPress=>{()=>screenRef.current.open()}/>
<MyComponent ref={screenRef}>
...
</View>
)
}
But I get warning message: Function components cannot be give refs. Attempts to access this ref will fail. Did you mean to use React.ForwardRef() ?
My questions is:
How to get rid of that warning & having MyScreen control the library component with reference hook?
======== UPDATE =======
I changed the name of the property in MyComponent from ref to innerRef like #Rishabh Anand suggested. The warning disappeared.
const MyComponent = ({innerRef}) => {
...
}
MyScreen is now:
export const MyScreen = () => {
const screenRef = useRef();
...
return (
<View style={styles.container}>
<Button onPress=>{()=>screenRef.current.open()}/>
<MyComponent innerRef={screenRef}>
...
</View>
)
}
But now a new issue comes, when I click on the Button of MyScreen, app crashed with error undefined is not an object (evaluating screenRef.current.open). It seems the screenRef.current holds a null instead of the library component. Why?
You have to use React.forwardRef
From the docs: https://fr.reactjs.org/docs/forwarding-refs.html
const FancyButton = React.forwardRef((props, ref) => (
<button ref={ref} className="FancyButton">
{props.children}
</button>
));
const ref = React.createRef();
<FancyButton ref={ref}>Cliquez ici</FancyButton>;
You can after you set a ref, call some function inside your child component like this:
const FancyButton = React.forwardRef((props, ref) => {
useImperativeHandle(ref, () => ({
click: () => {
onClick();
},
}));
const onClick = () => {
console.log('Clicked.');
};
return (
<button onClick={onClick} className="FancyButton">
{props.children}
</button>
);
});
const ref = React.createRef();
<FancyButton ref={ref}>Cliquez ici</FancyButton>;
And call from your parent component
ref.current.click()

Making navigation available to all components in React Native app

I'm using React Navigation 5 in my React Native app and I'm not exactly clear about how to make navigation available to all components. I also want to mention that my app uses Redux for state management but I didn't integrate React Navigation with Redux -- maybe I should!
My App.js renders my Navigator.js component which looks like below. BTW, my app requires authentication and one of the key functions of the navigator function is to redirect unauthenticated users to login screen.
class Navigator extends Component {
render() {
return (
<NavigationContainer>
{
this.props.isAuthenticated
? <MainMenuDrawer.Navigator drawerContent={(navigation) => <DrawerContent member={this.props.member} navigation={navigation} drawerActions={DrawerActions} handleClickLogOut={this.handleClickLogOut} />}>
<MainMenuDrawer.Screen name="Home" component={HomeStack} />
<MainMenuDrawer.Screen name="ToDoList" component={ToDoListStack} />
</MainMenuDrawer.Navigator>
: <SignInStack />
}
</NavigationContainer>
);
}
}
Then in my HomeStack, I have my Dashboard component which happens to be a class component that needs to access navigation. Here's my HomeStack:
const HomeStackNav = new createStackNavigator();
const HomeStack = () => {
return(
<HomeStackNav.Navigator screenOptions={{ headerShown: false }}>
<HomeStackNav.Screen name="DashboardScreen" component={Dashboard} />
<HomeStackNav.Screen name="SomeOtherScreen" component={SomeOtherComponent} />
</HomeStackNav.Navigator>
);
}
export default HomeStack;
Say, my Dashboard component looks like below. How do I make navigation available to this component?
class Dashboard extends Component {
handleNav() {
// Need to use navigation here...
}
render() {
return (
<Text>Welcome to Dashboard</Text>
<Button onPress={() => this.handleNav()}>Go somewhere</Button>
);
}
}
function mapStateToProps(state) {
return {
member: state.app.member
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(appActions, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Dashboard);
Do I need to pass navigation manually to each component? That seems too repetitive and prone to error. How do I make navigation available throughout my app?
Solution 1:
you can use useNavigation hook for functional component
import {useNavigation} from '#react-navigation/native';
const navigation = useNavigation();
navigation.navigate("interests");
//or
navigation.push("interests");
Solution 2:
you can use HOC withNavigation to navigation in props in any component for class component Ref
you can install #react-navigation/compat by
yarn add #react-navigation/compat
You can import like below
import { withNavigation } from '#react-navigation/compat';
you can use withNavigation like below
export default withNavigation(Dashboard)
Note: then you can use this.props.navigation in Dashboard component

React navigation. Render extra views on top of navigator

I'm trying to create a React Native app that renders an area with interchangeable screens and fixed view with some additional data/menu. I tried this solution form official React Navigation, but I can't make it work.
import React, { Component } from "react";
import {createStackNavigator} from 'react-navigation';
import { Text, View,} from 'react-native';
import Details from './DetailsScreen';
import MainScreen from './MainScreen';
const RootStack = createStackNavigator({
Main: {screen: MainScreen,
navigationOptions: {
header: null
}
},
Details: {
screen: Details,
},);
class App extends Component {
static router = {
...RootStack.router,
getStateForAction: (action, lastState) => {
return MyStack.router.getStateForAction(action, lastState);
}
};
render() {
const { navigation } = this.props;
return(
<View>
<Text>Foo</Text> //this is rendering
<RootStack navigation={navigation}/> //this is not
</View>);
}
}
export default App;
Article form link suggests that I can wrap object created with createStackNavigator() in a parent View, but it renders only when it is the only thing returned from render(), otherwise it seems to be ignored. Example from link claims that it is possible to "render additional things", but I can't make it work. Is it possible to do this way?
You can try this:
//wrapper.js
function wrapNavigator(Navigator, Wrapper, wrapperProps = {}) {
const WrappedComponent = props => (
<Wrapper { ...props, ...wrapperProps}>
<Navigator {...props} />
</Wrapper>
);
WrappedComponent.router = Navigator.router;
WrappedComponent.navigationOptions = Navigator.navigationOptions;
return WrappedComponent;
};
//app.js
const App = (props) => <View>
<Text>Some text...</Text>
{children}
</View>
export default wrapNavigator(Stack, App);
But this will not work if there are no parent navigator for App component.