React Native StackNavigator: Passing props from a screen to the header of the next screen - react-native

I have a React Native StackNavigator like so:
const AppStack = () => {
return (
<NavigationContainer theme={{ colors: { background: "white" }}}>
<Stack.Navigator headerMode="screen">
<Stack.Screen name="Master" component={ Master } />
<Stack.Screen
name="Details"
component={ Details }
options={{ headerTitle: props => <Header {...props} /> }} // <-- how can I pass props from Master to the Header here
/>
</Stack.Navigator>
</NavigationContainer>
)
}
The navigation works fine. When I'm on Master, if I press a TouchableOpacity, it brings up Details, with the header component Header.
However, what I want is to pass props from Master to the Header component in Details.
Something like this:
{/* What I want is that onPress, I want to pass someones_name and someones_photo_url to
the Details' Header component */ }
const Master = () => {
const someones_name = "Steve";
const someones_photo_url = "http://somephoto.com/001.jpg";
return (
<TouchableOpacity onPress={() => navigation.navigate("Details")}>
<Text>{ someones_name }</Text>
<Image source={{ uri: someones_photo_url }}>
</TouchableOpacity>
)
}
Is this possible?

You can pass values to other screens when navigating by doing the following:
Assume I am on screen "Feed" and click on someones name thus I want to go to screen "Profile" where it will show that user name that I clicked on.
In screen Feed
props.navigation.navigate('Profile, { userName: 'Shane' })`
In screen Profile I can grab that value passed in via navigate with:
const { userName } = props.route.params
To set this up inside a header assuming your screen options are outside the screen component and cannot access the props. Look into using setOptions to configure your header title dynamically.
props.navigation.setOptions({
headerTitle: ...
})

Related

passing text input between custom header using stack navigation

I wanted to connect the SearchMain, SearchHistoryList, and SearchResult screens using the custom header of the stack navigation.
At first, the SearchMain screen appeared,
and when the TextInput in the custom header is onFocus, the SearchHistory screen appeared,
and the SearchResult screen appeared when the TextInput is onSubmitEditing or when the SearchButton was pressed.
Also, I set the keyboard focus to be maintained when switching screens, and use Keyboard.discuss() when I want to close it.
However, if you enter something at the TextInput in custom header immediately after the screen changes, it will be entered at the TextInput on the previous screen.
I think it's because the three screens don't share a single custom header!! each header is created, and the keyboard focus on the previous screen!!
I want all screens use the same header, or the values of the headers are consistent.
What should I do to solve this problem?
I searched variously such as singleton header, static header, and header value consistent, but there is no good solution.
I recently started studying React Native, so I'm not sure about my method, and a completely different approach is also welcome~~
Below is my code, thanks.
const Stack = createStackNavigator();
function NavigationBar({navigation}){
const [query, setQuery] = React.useState('');
function changeSearchQuery(text : string) : void {
setQuery(text);
}
function deleteSearchQuery() : void {
setQuery("");
}
return(
<InputView>
<BackIcon onPress={() => {navigation.goBack()
Keyboard.dismiss()}}>
<Icon name="arrow-back-ios" size={widthPercentage(24)} color="#666666"/>
</BackIcon>
<InputText
keyboardType="default"
maxLength={100}
onChangeText={(str) => setQuery(str)}
value = {query}
placeholder="검색어 입력"
placeholderTextColor="#E9E9E9"
returnKeyType="search"
onFocus={() => navigation.navigate("SearchHistory", {changeSearchQuery:changeSearchQuery})}
onSubmitEditing={() => {navigation.navigate("SearchResult", {changeSearchQuery:changeSearchQuery})
Keyboard.dismiss()}}
allowFontScaling= {false}
/>
{ query.length > 0 &&
<DeleteIcon onPress={() => deleteSearchQuery()}>
<Icon name="clear" size={widthPercentage(20)} color="#666666"/>
</DeleteIcon>
}
<SearchIcon onPress={() => {navigation.navigate("SearchResult")
Keyboard.dismiss()}}>
<Icon name="search" size={widthPercentage(20)} color="#666666"/>
</SearchIcon>
<Line></Line>
</InputView>
);
}
export const Search = () =>{
return (
<Stack.Navigator initialRouteName="SearchMain" keyboardHandlingEnabled={false} screenOptions={{header:props => <NavigationBar navigation={props.navigation}/>}}>
<Stack.Screen name="SearchMain" component={SearchMain} options={{animationEnabled: false}}/>
<Stack.Screen name="SearchHistory" component={SearchHistory} options={{animationEnabled: false}}/>
<Stack.Screen name="SearchResult" component={SearchResult} options={{animationEnabled: false}}/>
<Stack.Screen name="SearchFilter" component={SearchFilter} options={{headerShown: false}}/>
</Stack.Navigator>
);
}
export default Search;
It's not a good pattern to set the state inside the header, instead you should centralized the state in your component and then use navigation.setOption to pass functions/state value.
In this way your header will look like this:
function NavigationBar({
query,
onFocus,
onChangeText,
onPressBackIcon,
onSubmitEditing,
onDeleteSearch,
onSearch
}){
return(
<InputView>
<BackIcon onPress={onPressBackIcon}>
<Icon name="arrow-back-ios" size={widthPercentage(24)} color="#666666"/>
</BackIcon>
<InputText
keyboardType="default"
maxLength={100}
onChangeText={onChangeText}
value = {query}
placeholder="검색어 입력"
placeholderTextColor="#E9E9E9"
returnKeyType="search"
onFocus={onFocus}
onSubmitEditing={onSubmitEditing}
allowFontScaling= {false}
/>
{ query.length > 0 &&
<DeleteIcon onPress={onDeleteSearch}>
<Icon name="clear" size={widthPercentage(20)} color="#666666"/>
</DeleteIcon>
}
<SearchIcon onPress={onSearch}>
<Icon name="search" size={widthPercentage(20)} color="#666666"/>
</SearchIcon>
<Line></Line>
</InputView>
);
}
Than you will have in SearchMainScreen your state, your functions and your custom header:
// remember headerMode='none' in screen option
const SearchMainScreen = () => {
const [query, setQuery] = React.useState('');
const onFocus = () => {}
...
return (
<View>
<NavigationBar
onFocus={onFocus}
query={query}
...
/>
{
(isFocus && !query) ?
<SearchHistoryComponent/> :
(isFocus && query) ?
<SearchResultComponent/> :
<List/>
}
</View>
)
}

How to update navigation header from screen?

react-native version 0.62
I am making a quiz app and on every new question(a new quiz screen, QuestionScreen.js), I want to update a label in the navigation bar which is declared in App.js
In App.js, the problem here is I don't know how to pass the state questionIndex to HomeScreen or QuestionScreen, normally I thought this would be using props like <Question questionIndex={questionIndex}>. I tried to pass them in options but that doesn't seem to work...
const App = () => {
const [questionIndex, setQuestionIndex] = useState(0);
return (
<NavigationContainer>
<Stack.Navigator initialRouteName='Home'
>
<Stack.Screen
name='Home'
component={HomeScreen}
options={{
title:''
}}
/>
<Stack.Screen
name='QuestionScreen'
component={QuestionScreen}
options={({ navigation }) => ({
headerBackTitleVisible: false,
headerRight: ()=>(<HeaderRight pageIndex={questionIndex}/>),
headerRightContainerStyle: {
alignItems: "flex-end",
paddingRight: 25
},
})}
/>
function HeaderRight(props) {
return (
<Text>{props.pageIndex}/{questions.length-1}</Text>
);
}
const HomeScreen = ({ navigation }) => {
return (
<View style={styles.container} >
<ScrollView>
<TouchableOpacity onPress={()=>
//here I'm unable to retrieve the questionIndex props.
//tried navigation.getParams("questionIndex") or route.params
navigation.navigate('QuestionScreen', {
pageIndex: 0
})
}>
In QuestionScreen.js
function QuestionScreen({route, navigation, options}) {
const { pageIndex } = route.params;
[Full code here on github]https://github.com/liaoxsong/expofirst
I am a newbie to react-native and I come from native mobile development, this Stack.Navigator api looks weird, can't I just manage the navigation header from the screen component itself? That would make things easier.
it is difficult to customize the default header given by react navigation, what you can do is disabling the header and creating your own custom header.
disable header by giving prop headerMode={"none"} in createStackNavigator and have your own custom header on top,
to disable header docs : https://reactnavigation.org/docs/stack-navigator/#headermode

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.

Adding a back button for each bottom tab with React Navigation

The bottom tabs navigation is looking something like that:
const Tab = createBottomTabNavigator();
export default function TabStackScreen() {
return (
<Tab.Navigator initialRouteName="Home">
<Tab.Screen name="Home" component={HomeStackScreen} />
<Tab.Screen name="Favorites" component={FavoritesStackScreen} />
<Tab.Screen name="Search" component={SearchStackScreen} />
</Tab.Navigator>
)
}
There is not back button on the favorites and search screen. I guess this is the normal behavior, but I would love to have one.
I did not find something relevant in the doc, except recreating a component that looks like the native back button and adding it on some screens using headerLeft. Is there a more simple method?
In my projects I like to create custom headers, and I do not show the default header and show my own custom header component.
On my custom component I add right and left components, and let the screens decide what to do, by default the back button is shown, but if the screen pass the noBack property, the button is hidden.
You can also add a right component, for example a close button.
That is what I use to do:
const screenOptions = {
headerShown: false
};
<RootStack.Navigator screenOptions={screenOptions} mode="modal">
and then create my own component
export const Header = ({ title, leftComponent, rightComponent, noBack }) => {
const navigation = useNavigation();
return (
<Wrapper>
{leftComponent ||
(noBack ? (
<Placeholder />
) : (
<Button
onPress={() => navigation.goBack()}
accessible
accessibilityRole="button"
accessibilityLabel="Back">
<Icon
size={30}
name={Platform.OS === 'android' ? 'arrow-left' : 'chevron-left'}
/>
</Button>
))}
<Title bold accessibilityRole="header" acessible acessibilityText={title}>
{title}
</Title>
{rightComponent || <Placeholder />}
</Wrapper>
);
};
Doing that, you are able to customize everything on your header, it works like a charm for me.

Refreshing React Navigation Tab Navigator Icon

My app is using the latest hook-based implementation of React Navigation (v5.4.0). It has a tab bar navigator that has a tab representing mail messages, with the obligatory number of messages shown in the corner of the icon. The tab code looks something like:
var messageCount = 0;
export function UpdateMessageCount(newnum:number)
{
messageCount = newnum;
}
function MessageIcon (color:string, size:number)
{
return (messageCount < 1)? (<FontAwesome style={{color:color, fontSize:size}} icon={LightIcons.envelope} pro={true} />):
(<View>
<FontAwesome style={{color:color, fontSize:size}} icon={LightIcons.envelope} pro={true} />
<View style={{position:'absolute',borderRadius:20, right:-10,top:0,backgroundColor:'red',minWidth:15, height:15, justifyContent:'center', alignItems:'center'}}>
<Text style={{fontSize:11, color:'white', fontFamily:'Roboto-Medium',marginLeft:2,marginRight:2}}>{messageCount}</Text>
</View>
</View>)
}
and
const MainTab = createBottomTabNavigator();
function MainTabNavigator() {
return (
<MainTab.Navigator
...
<MainTab.Screen name="Messages"
component={ MessageListNavigator }
options={{
tabBarIcon: ({ color, size }) => (
MessageIcon(color, size)
),
}}/>
I want the icon/tab navigator to refresh when the number of mail messages has changed (which happens asynchronously via a sync). Setting a fake option (as shown here) on the navigator will cause a tab navigator refresh, but is relying on an undocumented side effect:
UpdateMessageCount(count);
let op = this.props.navigation.dangerouslyGetParent();
op.setOptions({tickle:""})
1) Are there alternative ways of telling the tab navigator/icon to refresh? (I'm not using redux)
2) Is there a way to get the handle to a nested tab navigator from the main navigator object?
Thanks in advance for suggestions and comments.
I solved this by emitting an event when the message count changes, and catching the event in the tab navigator hook. Slightly revised code looks like:
function MessageIcon (messageCount:number, color:string, size:number)
{
return (messageCount < 1)? (<FontAwesome style={{color:color, fontSize:size}} icon={LightIcons.envelope} pro={true} />):
(<View>
<FontAwesome style={{color:color, fontSize:size}} icon={LightIcons.envelope} pro={true} />
<View style={{position:'absolute',borderRadius:20, right:-10,top:0,backgroundColor:'red',minWidth:15, height:15, justifyContent:'center', alignItems:'center'}}>
<Text style={{fontSize:11, color:'white', fontFamily:'Roboto-Medium',marginLeft:2,marginRight:2}}>{messageCount}</Text>
</View>
</View>)
}
const MainTab = createBottomTabNavigator();
function MainTabNavigator() {
const [messageCount, setMessageCount] = useState(0);
const messageCountListener = MyEvents.addListener(MyEventMessageCountUpdated,
(count)=>{
console.log("message count update: ", count)
setMessageCount(count);
});
useEffect(()=> {
return function cleanup() {
messageCountListener.remove();
}
});
return (
<MainTab.Navigator
...
<MainTab.Screen name="Messages"
component={ MessageListNavigator }
options={{
tabBarIcon: ({ color, size }) => (
MessageIcon(messageCount, color, size)
),
}}/>