On one screen, I would like to have a custom component to be shown on the header.
I tried:
const MyScreen = ({route, navigation}) => {
navigation.setOptions({
headerTitle: props => {
<MyComponent {...props} />;
},
});
...
export default MyScreen;
But it shows blank like below:
To further verify the approach is correct, I also tried to simply show a text:
navigation.setOptions({
headerTitle: props => {
<Text {...props}> Hello </Text>;
},
});
It also shows blank in the header title area. What am I missing? How to show a custom component in the header?
(MyScreen is hosted by a stack navigator)
The issue is that you are not returning the component, hence why it is not displayed.
You should either write:
const MyScreen = ({route, navigation}) => {
React.useLayoutEffect(() => {
navigation.setOptions({
headerTitle: props => {
return <MyComponent {...props} />;
},
});
}, [navigation]);
...
export default MyScreen;
or for a more concise syntax:
const MyScreen = ({route, navigation}) => {
React.useLayoutEffect(() => {
navigation.setOptions({
headerTitle: props => <MyComponent {...props} />,
});
}, [navigation]);
...
export default MyScreen;
You also need to use useLayoutEffect because you cannot update a component from inside the function body of a different component.
useLayoutEffect runs synchronously after a render but before the screen is visually updated, which will prevent your screen to flicker when your title is displayed.
By the way, since you seemingly don't need to use a function as props of your component, you could define this option directly in your navigator like so:
<Stack.Navigator>
<Stack.Screen
name="MyScreen"
component={MyScreen}
options={{ headerTitle: props => <MyComponent {...props} /> }}
/>
</Stack.Navigator>
Related
I am want to add a secondary Navigation so that a subset of screens have access to providers, as opposed to the whole stack being wrapped by providers they do not need access to. However, I think there is something wrong with my nested setup because I get an error when clicking a button that would navigate to the substack:
ERROR The action 'NAVIGATE' with payload {"name":"VehicleDetailInspections","params":{"vehicleId":27541}} was not handled by any navigator.
Do you have a screen named 'VehicleDetailInspections'?
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.
The link to this only shows tabs & drawers, which we do not have.
My setup is:
HOMESTACK
const HomeStack = createNativeStackNavigator<HomeStackParams>();
export function HomeNavigator(): JSX.Element {
const styles = useInspectionStyles();
return (
<VehicleDataProvider>
<HomeStack.Navigator
screenOptions={{
headerStyle: styles.headerStyles,
headerBackVisible: false,
headerTitle: '',
headerLeft: () => <GoBack />,
}}
>
<HomeStack.Group
screenOptions={{
headerShadowVisible: false,
headerStyle: [styles.headerStyles],
}}
>
<HomeStack.Screen
name={VEHICLE_DETAIL}
component={VEHICLE_DETAIL.component}
/>
</HomeStack.Group>
/*
// I have tried both of these options:
<HomeStack.Group>
{() => <InspectionNavigator />}
</HomeStack.Group>
// or
<HomeStack.Screen
name={'InspectionSubStack'}
component={InspectionNavigator}
/>
*/
</HomeStack.Navigator>
</VehicleDataProvider>
);
}
In the above HomeStack, in the VEHICLE_DETAIL screen, there is a button which when clicked navigates to VEHICLE_DETAIL_INSPECTIONS which is part of the InspectionStack below.
const navigation = useNavigation<NavigationProp<InspectionStackParams>>();
...
onPress={() =>
navigation.navigate('VEHICLE_DETAIL_INSPECTIONS', {
vehicleId: vehicle.id,
})
}
INSPECTION substack:
const InspectionStack = createNativeStackNavigator<InspectionStackParams>();
export function InspectionNavigator(): JSX.Element {
const styles = useInspectionStyles();
return (
<InspectionsDataProvider>
<InspectionProgresssProvider>
<InspectionStack.Navigator
screenOptions={{
headerStyle: styles.headerStyles,
headerBackVisible: false,
headerTitle: '',
headerLeft: () => <GoBack />,
}}
>
<InspectionStack.Screen
name={VEHICLE_DETAIL_INSPECTIONS}
component={
VEHICLE_DETAIL_INSPECTIONS.component
}
/>
<InspectionStack.Screen
name={VEHICLE_INSPECTIONS_SECTION].name}
component={
VEHICLE_INSPECTIONS_SECTION.component
}
/>
<InspectionStack.Screen
name={VEHICLE_INSPECTIONS_STEP}
component={VEHICLE_INSPECTIONS_STEP.component}
/>
</InspectionStack.Navigator>
</InspectionProgresssProvider>
</InspectionsDataProvider>
);
}
What do I need to do to get the InspectionNavigator to be found by the navigate() from HomeStack?
According to the React-Navigation docs (https://reactnavigation.org/docs/header-buttons/), in the header, we can interact with screen's state:
function StackScreen() {
return (
<Stack.Navigator>
<Stack.Screen
name="Home"
component={HomeScreen}
options={({ navigation, route }) => ({
headerTitle: props => <LogoTitle {...props} />,
})}
/>
</Stack.Navigator>
);
}
function HomeScreen({ navigation }) {
const [count, setCount] = React.useState(0);
React.useLayoutEffect(() => {
navigation.setOptions({
headerRight: () => (
<Button onPress={() => setCount(c => c + 1)} title="Update count" /> // <===== SET STATE
),
});
}, [navigation]);
return <Text>Count: {count}</Text>;
}
It works fine when I set screen's state from header. But when I try to get screen's state from header, it's always return the initial value of state.
<Button
onPress={() => {
setCount(c => c + 1);
alert(count)
}}
title="Update count" />
How can I get the current state of screen from header?
I've had the solution:
Problem is in the React.useLayoutEffect() function.
I need to pass [count] to the second argument of this function instead of [navigation]:
React.useLayoutEffect(()=>{
...
}, [count]);
Then, it works fine
There are many similar questions that have been answered but none of them uses the newest react-navigation version.
I want to go to the 'Home' screen from the 'Document' screen on back button press. This is what I tried but it doesn't work.
<NavigationContainer>
<Stack.Navigator initialRouteName="Home" screenOptions={{ headerShown : false }}>
<Stack.Screen name="Home" component={Home} />
<Stack.Screen name="Camera" component={CameraScreen} />
<Stack.Screen name="Document" component={Document} options={{
headerLeft: (props) => (<HeaderBackButton {...props} onPress={() => props.navigation.navigate("Home")}/>)
}} />
</Stack.Navigator>
</NavigationContainer>
Edit for more clarification: My app starts with 'Home' then goes to 'Camera' then to 'Document'. Now, I don't want to go back to 'Camera' once I am at 'Document' rather straight to 'Home' when I press the phone's back button.
According to the documentation, this is how to override the back button.
<Screen
name="Home"
component={HomeScreen}
options={{
headerLeft: (props) => (
<HeaderBackButton
{...props}
onPress={() => {
// Do something
}}
/>
),
}}/>;
But I don't know how to go to 'Home' using the above code. So I searched similar questions and most of them had navigationOptions and other stuff. I tried following the below answer from a question.
import { HeaderBackButton } from 'react-navigation';
static navigationOptions = ({navigation}) => {
return{
headerLeft:(<HeaderBackButton onPress={()=>{navigation.navigate('A')}}/>)
}
}
So yeah, the back button doesn't respond even if I use console.log
You can customise back button behaviour using navigation 'beforeRemove' event, which fires before unloading a screen.
useEffect(() => {
const unsubscribe = navigation.addListener('beforeRemove', e => {
e.preventDefault(); // Prevent default action
unsubscribe() // Unsubscribe the event on first call to prevent infinite loop
navigation.navigate('Home') // Navigate to your desired screen
});
}, [])
The positive point with this approach is that it will fire on any type of back navigation, whether it's in-app back button or device back button/gesture.
Navigation Events Doc
I managed to achieve what I needed. I had to override the physical back button in 'Document'.
useFocusEffect(
useCallback(() => {
const onBackPress = () => {
navigation.pop(2); // remove two screens i.e. Document and Camera
return true // disable normal behaviour
};
BackHandler.addEventListener('hardwareBackPress', onBackPress); // detect back button press
return () =>
BackHandler.removeEventListener('hardwareBackPress');
}, [])
);
I am not sure how your code works.
<it doesn't work.> - you can give us more information.
But it's worth to try below.
import {CommonActions} from '#react-navigation/native';
props.navigation.dispatch(
CommonActions.navigate({name: 'Home'}),
);
https://reactnavigation.org/docs/navigation-prop/#dispatch
Did you try CommonActions.reset ?
something like this from the doc :
import { CommonActions } from '#react-navigation/native';
// when you want to navigate to the Documents page, instead of doing
navigation.navigate({routeName: 'Documents'});
// you can try
navigation.dispatch(
CommonActions.reset({
index: 1,
routes: [
{ name: 'Home' },
{
name: 'Documents',
params: { name: 'abc' },
},
],
})
);
So that when you're going back from Documents you're heading to the previous screen in the stack : Home.
I am currently trying to implement a custom tabBar design into my react native app which is using React Navigation 5 as the navigation library. Everything is working correctly, except that my tabBarIcons don't receive any props, so i cannot determine whether i have to show the active or inactive tabIcon. Whenever i use a default tabbar i do receive the props, so there must be something wrong in my custom tabbar. I did follow the docs though, and only find the instruction to emit the 'tabPress' event. I do however think that i should emit more events to get the correct focused prop. I have set up the navigator like this:
const Tabs = createBottomTabNavigator();
export default () => (
<Tabs.Navigator tabBar={TabBarComponent} initialRouteName="Home">
<Tabs.Screen
name="Home"
component={HomeScreen}
options={{
tabBarIcon: ({ focused }) => {
// The props here are {}, so focused is undefined.
const icon = focused
? require('images/iconOverviewRed.png')
: require('images/iconOverviewGrey.png');
return <Image source={icon} />;
},
}}
/>
<Tabs.Screen
name="Overview"
component={OverviewScreen}
options={{
tabBarIcon: props => {
console.log(props);
return <Image source={require('images/logoRed.png')} />;
},
}}
/>
<Tabs.Screen
name="Account"
component={AccountScreen}
options={{
tabBarIcon: ({ focused }) => {
const icon = focused
? require('images/iconAccountRed.png')
: require('images/iconAccountGrey.png');
return <Image source={icon} resizeMethod="resize" />;
},
}}
/>
</Tabs.Navigator>
);
And this is my custom tabBar compnent:
const TabBar = ({ navigation, state, descriptors }: any) => {
return (
<View style={styles.container}>
{state.routes.map((route: any) => {
const onPress = () => {
const event = navigation.emit({
type: 'tabPress',
target: route.key,
canPreventDefault: true,
});
if (!event.defaultPrevented) {
navigation.dispatch({
...TabActions.jumpTo(route.name),
target: state.key,
});
}
};
return (
<TabIcon
key={route.key}
Icon={descriptors[route.key].options.tabBarIcon}
onPress={onPress}
isBig={route.name === 'Home'}
/>
);
})}
</View>
);
};
const TabIcon = ({ onPress, Icon, key, isBig }: any) => {
return (
<TouchableWithoutFeedback key={key} onPress={onPress}>
<View style={isBig ? styles.bigTab : styles.defaultTab} key={key}>
<Icon />
</View>
</TouchableWithoutFeedback>
);
};
Thanks in advance.
descriptors[route.key].options just gives you the options as you have specified them. If you log the value of descriptors[route.key].options.tabBarIcon, you'll see that it prints the function that you have specified.
In your custom tab bar, it's upto you to use the option as you need. Since it's a function here, you'll have to call it and pass desired arguments.
descriptors[route.key].options.tabBarIcon({ focused: state.index === index })
This also means that you fully control the option. You can put whatever type you'd like, function, a require statement directly etc. and then use that. You also don't have to call it tabBarIcon, you can call it whatever you want.
I have only been working with React-Native recently. For this reason, I would need help with the following problem:
I did override the back button action in the header of a stack navigator, to navigate to any screen. I have to "send back" props to the screen xy. I have tried it on several variants, but unfortunately get error messages (because it does not work as I want) or do not get the expected result. I would be grateful for a solution. Thank you!
static navigationOptions = ({navigation}) => ({
title: null,
headerStyle: {backgroundColor: 'transparent'},
headerLeft: <HeaderBackButton
onPress={() => {
this.props.navigation.state.params.returnData(CurrentServiceProvider,'ServiceProvider')
navigation.push('digitalCode'),{ Info1: SomeObject, Info2: SomeObject }
}}
/>
})
Edit:
Screen B:
export class ScreenB extends React.Component {
static navigationOptions = ({navigation}) => ({
title: null,
headerStyle: {backgroundColor: 'transparent'},
headerLeft: <HeaderBackButton onPress={() => {
navigation.state.params.goBackData({SomeObject, 'ObjectIdentifier'});
navigation.push('ScreenA')}
}
/>
})
}
Screen A:
export class ScreenA extends React.Component {
getBackData = (data) => console.log(data)
ForwardNextScreen(){
this.props.navigation.navigate('ScreenB', {goBackData: this.getBackData});
}
}
If you want to send data back to previous screen ,the correct way is to declare callback on navigationParams (Github issue)
ScreenA
getBackData = (data) => console.log(data)
changeScreen = () => this.props.navigation.navigate('ScreenB', { goBackData: this.getBackData })
ScreenB
this.props.navigation.state.params.goBackData({
//your data json or whatever
})
this.props.navigation.pop()
Edit
Please change your from
headerLeft: <HeaderBackButton onPress={() => {
navigation.state.params.goBackData({SomeObject, 'ObjectIdentifier'});
navigation.push('ScreenA')}
}
/>
to
headerLeft: <HeaderBackButton onPress={() => {
navigation.getParam('goBackData')({SomeObject, 'ObjectIdentifier'});
navigation.pop();
}
/>