How to update navigation header from screen? - react-native

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

Related

How do I pass certain route parameters from a screen function to a navigation header button in React Native

I am new to react native and am basically wondering how to pass information from one screen to another if the button that triggers the shift from one screen to another is in the navigation class.
For example, If I have a screen:
export default function Screen() {
const data = ["a", "b", "c"]
}
and another screen with a SectionList section that requires that information:
const data = [
{
title: "",
data: {route.params?.data},
},
and finally within the navigation tab a custom navbar header:
<Stack.Screen
name="Screen"
component={ChatScreen}
options={({ route }) => ({
headerRight: () => (
<Pressable onPress={() => navigation.navigate("Screen", {data: *get data from
Screen here*)}>
<Ionicons
name="ellipsis-horizontal"
size={25}
color="#5C6AEF"
style={{ marginBottom: 2 }}
/>
</Pressable>
),
})}
/>
basically, how do I call the information from Screen in the Navigation tab to pass to the SectionList screen?
Thanks for the help!

How to navigate to other screen using the headerRight on the navigator code?

The title is very confusing, but the explanation that I will give will be more clear.
I am creating one StackNavigator for my app, and I am defining one icon to be displayed on the header of one of my screens like so:
const navigator= createStackNavigator(
{
Initial: {
screen: Posts,
navigationOptions: {
title: "All Posts",
headerRight: () => {
return (
<TouchableOpacity style={{ padding: 5 }}>
<Feather name={"plus"} size={30} />
</TouchableOpacity>
);
},
},
},
Details: Details,
Create: Create,
},
{
initialRouteName: "Initial",
}
);
const App = createAppContainer(navigator);
export default () => {
return (
<Provider>
<App />
</Provider>
);
};
The problem is that I want to navigate the user to the Create screen when the user presses the icon that I am rendering on the right side of the header, but I do not know how to have access to the navigation.navigate() function that the navigator generates for all the screens. I know that I can define the header on the Posts' screen file, so I have access to the navigation.navigate() function, but I want to know if there is some way to do it on the App.js file.
EDIT
Reading the documentation I saw that the way that I am was creating the navigator is not what the official documentation recommends. I learned to make it like this by one old course, using React Navigation 4.x, and now with React Navigation 6.x I perceived the difference in the creation and changed the way that I was doing it on my app. You can check the documentation for the problem that I was having here
You can prepare your navigation option this way by sending props
options={(props) => ({
headerRight: () => <TouchableOpacity
onPress={()=>props.navigation.navigate('Screen')} style={{ padding: 5 }}>
<Feather name={"plus"} size={30} />
</TouchableOpacity>
})}

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

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: ...
})

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)
),
}}/>

How to implement a shared Search Bar input across stack/tab navigators using react-navigation?

Snapchat's UI is currently setup with a floating SearchBar header that appears to be shared across a few screens/tabs. I'd like to replicate the shared SearchBar header using react-navigation. I currently have a half working solution...
Currently even though I have the headerTitle set on the StackNavigator, it appears that the header is rendering a brand new SearchBar (you can see the slight flicker indicating its rendering) upon navigation to the search results screen.
Here is the setup I have currently for one of the Stacks inside my TabNavigator.
function NetworkStack({ route, navigation }) {
return (
<Network.Navigator
initialRouteName="NetworkEventList"
screenOptions={({ navigation, route }) => ({
headerTitle: () => <Search navigation={navigation} route={route} stackName={"NetworkStack"}/>,
})}>
<Network.Screen
name="NetworkSearchResults"
component={SearchResults}
options={({ navigation, route }) => ({
//headerTitle: () => <Search navigation={navigation} route={route} focused={true} stackName={"NetworkStack"}/>,
headerBackImage: () => <BackButton navigation={navigation} shouldPop={true}/>,
headerBackTitleVisible: false,
gestureEnabled: true
})}/>
<Network.Screen
name="NetworkEventList"
component={NetworkEventList}
options={({ navigation, route }) => ({
headerLeft: () => <ProfileSidebarButton navigation={navigation}/>,
//headerTitle: () => <Search navigation={navigation} focused={false} stackName={"NetworkStack"}/>,
headerRight: () => <CommunityButton navigation={navigation} stackName={"NetworkStack"}/>
})}/>
</Network.Navigator>
)
}
Below is my TabNavigator.
function TabNavigator({ navigation, route }) {
return (
<Tab.Navigator
initialRouteName="NetworkStack"
tabBar={props => <TabBar {...props}/>}>
<Tab.Screen
name="CheckInStack"
component={CheckInStack}/>
<Tab.Screen
name="NetworkStack"
component={NetworkStack}/>
<Tab.Screen
name="MapStack"
component={MapStack}/>
</Tab.Navigator>
);
}
The logic that navigates to the search results component is inside the onFocus listener of the input. Here is the code for that...
const searchBarFocus = () => {
switch(props.stackName) {
case "MapStack":
var searchType = props.searchGoogle ? "AddEstablishment" : "ViewingEstablishments";
props.navigation.navigate('MapSearchResults', {searchType: searchType});
break;
case "NetworkStack":
props.addingMarkers(false);
var searchType = props.searchForPosting ? "ViewingEstablishments" : "ViewingUsers";
let index = null;
let routeState = props.route.state;
if(routeState) index = routeState.index;
if(index !== 1) {
console.log(props.navigation);
props.navigation.navigate('NetworkSearchResults', {searchType: searchType});
}
break;
case "CheckInStack":
props.addingMarkers(false);
props.navigation.navigate('CheckInSearchResults', {searchType: "ViewingUsers"});
break;
}
}
How would I go about configuring my navigation elements so that I have a singular SearchBar element that mounts one time? You can see in the gif I uploaded that the searchbar also loses focus upon navigation, this is also due to the second rendering/mounting of my Search component. Any suggestions would be much appreciated!
I was able to find a solution to my question. Feel free to comment or supply a better answer. I use the react-native-elements Search Bar to create my searching/input element at the header of my navigation stacks. Originally this was a View wrapping the SearchBar component. I changed this to a TouchableOpacity so that I could listen for onPress event. This is the new element I constructed. I kept the original navigation configuration that was supplied in the question.
return (
<TouchableOpacity style={[styles.mainContainer, {width: SEARCH_BAR_WIDTH_UNFOCUSED}]} onPress={() => searchBarPress()}>
<SearchBar
id={"searchBar-"+ props.stackName}
platform="ios"
ref={search => searchRef = search}
value={searchInput}
containerStyle={styles.searchInputContainerWrapper}
inputContainerStyle={styles.searchInputContainer}
inputStyle={{color: styles.inputContainer.color}}
round={true}
autoFocus={props.route.params ? true : false}
pointerEvents={props.route.params ? "auto" : "none"}
cancelButtonTitle="Cancel"
cancelButtonProps={{color: '#707070'}}
onChangeText={updateSearch}
//onFocus={searchBarFocus}
onCancel={() => console.log("Cancel")}
/>
</TouchableOpacity>
)
The key parts of creating the snapchat like search bar header is the autoFocus and pointerEvents properties. The property values needs to be dependent upon which screen the user is currently on.
I think, the good way is to create just a separate component with intention to be a searching component, then when searching happens the results is stored on the global state, as context or redux store
//search.js
const SearchBar = (props) => {...}; //which have access to the global state
//then on your routes
function StackScreen() {
return (
<Stack.Navigator>
<Stack.Screen
name="Home"
component={HomeScreen}
options={{
headerTitle: props => <LogoTitle {...props} />,
headerRight: () => (
<SearchBar/>
),
}}
/>
<Stack.Screen
name="Users"
component={UsersScreen}
options={{
headerTitle: props => <LogoTitle {...props} />,
headerRight: () => (
<SearchBar/>
),
}}
/>
</Stack.Navigator>
);
}