I have a header in my app that needs to render a different button depending on whether or not the user has notifications. This is how I currently have it set up in my pages:
static navigationOptions = ({ navigation }) => {
return {
title: 'My Profile',
headerRight: () => (
<Button
type="clear"
icon={() => <Icon
type="material-community"
size={25}
name={UserProvider.bNotifications ? 'bell' : 'bell-outline'}
color={UserProvider.bNotifications ? COLORS.WARNING : COLORS.WHITE}
/>}
onPress={() => navigation.navigate('Notifications', null)}
/>
)
}
};
The problem is, if UserProvider.bNotifications changes value, the header button doesn't update unless the page is changed / rerendered.
I want to switch to use the navigation property that is passed into those navigationOptions, but I don't no how to access it from outside the navigation stack. UserProvider is a static class (NOT a component) so I can't access the navigation prop through the usual manner (or by using the useNavigation hook).
I do have a NavigationProvider class that has access to the NavigationContainer for the app. I use it to trigger navigation without components. Is there some way I can set the params on the navigation property using that same reference?
NavigationProvider:
import { NavigationActions } from 'react-navigation';
let _navigator;
function getNavigator() {
return _navigator;
}
function setTopLevelNavigator(navigatorRef) {
_navigator = navigatorRef;
}
function navigate(routeName, params) {
_navigator.dispatch(
NavigationActions.navigate({
routeName,
params,
})
);
}
function goBack() {
_navigator.dispatch(
NavigationActions.back()
);
}
export default {
navigate,
setTopLevelNavigator,
getNavigator,
goBack
};
The ref is set like this in my top level App component:
<AppContainer
ref={navigatorRef => {
console.log(navigatorRef.props.navigation);
NavigationProvider.setTopLevelNavigator(navigatorRef);
}}
/>
EDIT - UserProvider
This is just the gist of my UserProvider class, but should convey how it works.
export default class UserProvider {
private static _bNotifications: boolean;
static get bNotifications(): boolean {
if (!this.hasInitNotifications)
this.initNotficationWatch();
return this._bNotifications;
}
static set bNotifications(bNotifications: boolean) {
this._bNotifications = bNotifications;
}
static initNotficationWatch() {
//Firebase listener on notification
if(notifications){
this.bNotifications = true;
} else {
this.bNotifications = false;
}
}
}
How to set navigation params from outside navigation stack?
to do this you have to utilize the getParam method that comes from the navigation prop. the way I would di it would be to set a variable to a parameter that would equal to UserProvider.bNotifications.
static navigationOptions = ({ navigation }) => {
let notifications = navigation.getParam('notifications')
return {
title: 'My Profile',
headerRight: () => (
<Button
type="clear"
icon={() => <Icon
type="material-community"
size={25}
name={notifications ? 'bell' : 'bell-outline'}
color={notifications ? COLORS.WARNING : COLORS.WHITE}
/>}
onPress={() => navigation.navigate('Notifications', null)}
/>
)
}
};
you can set the param to an initial value by adding it as a second argument if needed navigation.getParam('paramName', 'param initial value')
To update the parameter you need to use the setParams method. For this you can use the useNavigation hook.
You can also do this inside of a functions instead of a element
// remember to const navigation = useNavigation()
<Button
title="Update param"
onPress={() => navigation.setParams({notifications: 'new value'})}
/>
you can also initiate the value this way... but I would recommend to initialize the value inside your navigationOptions
I haven't tested the code but it should work
RN DOCS: https://reactnavigation.org/docs/2.x/headers/
Related
If I'm using React Navigation v5, what is the best way to pass the current state of a parent component (in my case, the main App) down through a Tab and Stack navigator to a screen that I'd like to use the current state in?
Following the documentation, I have created a stack navigator for each tab that holds the respective screens.
App.js contains a state that needs to be used for a few things. Most importantly, it will provide badge count on the Tab navigator, as well as be a source of Flatlist data on one of the tab screens.
What is the correct approach to getting the state from App all the way down to a child component in a stack navigator in a tab navigator?
App.js
const Tab = createBottomTabNavigator()
export default class App extends React.Component {
constructor(props){
super(props)
this.state = {
neededArray: []
}
}
const updateTheArray = (newArray) => {
this.setState({
neededArray: newArray
})
}
componentDidMount(){
//Listener that searches for nearby bluetooth beacons and updates the array with the passed function
startObserver(updateTheArray)
}
componentWillUnmount(){
stopObserver()
}
render(){
return(
<NavigationContainer>
<Tab.Navigator>
<Tab.Screen
name = "Home"
component = { HomeStack }/>
<Tab.Screen
name = "About"
component = { AboutStack }/>
//The Stack that contains the screen that I need to use the App's state in
<Tab.Screen
name = "Nearby"
component = { NearbyStack }/>
</Tab.Navigator>
</NavigationContainer>
)
}
}
NearbyStack.js
//This stack holds the screen that I need to use the App's state in
const NearbyStackNav = createStackNav()
const NearbyStack = () => {
return(
<NearbyStackNav.Navigator>
<NearbyStackNav.Screen
name = "Nearby"
component = { NearbyScreen }
/>
</NearbyStackNav.Navigator>
)
}
NearbyScreen.js
//The screen that I want to use the App's state in
const NearbyScreen = () => {
return(
<View>
<FlatList
//Where I would like to use the App's state
/>
</View>
)
}
You can pass some initial params to a screen. If you didn't specify any params when navigating to this screen, the initial params will be used. They are also shallow merged with any params that you pass. Initial params can be specified with an initialParams prop:
Usage
<Tab.Screen
name = "Nearby"
component = { NearbyStack }
initialParams={{ arrayItem: this.state.neededArray }}
/>
NearbyScreen.js
React.useEffect(() => {
if (route.params?.arrayItem) {
// Post updated, do something with `route.params.arrayItem`
// For example, send the arrayItem to the server
}
}, [route.params?.arrayItem]);
My solution was to use React's Context API.
BeaconContext.js - New
import React from 'react'
const BeaconContext = React.createContext()
export default BeaconContext
App.js - Modified
import BeaconContext from './path/to/BeaconContext'
const Tab = createBottomTabNavigator()
export default class App extends React.Component {
constructor(props){
super(props)
this.state = {
neededArray: []
}
}
const updateTheArray = (newArray) => {
this.setState({
neededArray: newArray
})
}
componentDidMount(){
startObserver(updateTheArray)
}
componentWillUnmount(){
stopObserver()
}
render(){
return(
// Wrap the nav container in the newly created context!!!
<BeaconContext.Provider value = { this.state.neededArray }
<NavigationContainer>
<Tab.Navigator>
<Tab.Screen
name = "Home"
component = { HomeStack }/>
<Tab.Screen
name = "About"
component = { AboutStack }/>
<Tab.Screen
name = "Nearby"
component = { NearbyStack }/>
</Tab.Navigator>
</NavigationContainer>
</BeaconContext.Provider>
)
}
}
NearbyStack.js - Unchanged
const NearbyStackNav = createStackNav()
const NearbyStack = () => {
return(
<NearbyStackNav.Navigator>
<NearbyStackNav.Screen
name = "Nearby"
component = { NearbyScreen }
/>
</NearbyStackNav.Navigator>
)
}
NearbyScreen.js - Modified
import BeaconContext from './path/to/BeaconContext'
const NearbyScreen = () => {
return(
<View>
//Wrap the component in the new context's consumer!!!
<BeaconContext.Consumer>
{
context => <Text>{ context }</Text>
}
</BeaconContext.Consumer>
</View>
)
}
I've been struggling with the exact same issue - when using the initialProps property to pass a state to a Tab.Screen the screen never receives any updates. It reads the intial state value once then nothing.
To make it work I skipped using the initialProps property and instead used the children property on Tab.Screen like so:
App containing <Tab.Navigator> and <Tab.Screen>:
const[myBool, setMyBool] = useState(false)
<Tab.Screen
name="MyTab"
children={() => (
<MySecondScreen passedStateParam={ myBool } />
)}
.
.
.
</Tab.Screen>
MySecondScreen consuming updates on passed myBool state:
export function MySecondScreen ({ passedStateParam }) {
const myPassedBoolState = passedStateParam
React.useEffect(() => {
if(myPassedBoolState) {
//Act upon App.tsx updating the state
}
}, [myPassedBoolState])
}
Not sure if I'm missing something when trying to perform this with the initialParams property but this way (using children property) I got it to work at least.
I'm using react navigation in my RN app and trying to implement a form to submit some information. I'm using a button at the right header and want to style the button with different color to indicate whether the form is legal (e.g., white for a legal form and transparant for having important inputs left blank).
I use this.state.submitDisabled to indicate its legality and define the right header in componentDidMount() and pass the navigation param to the header to render in navigationOptions:
this.props.navigation.setParams({
headerRight: (
<MaterialHeaderButtons>
<Item title="submit" iconName="check"
color={this.state.submitDisabled ? colors.white_disabled : colors.white}
onPress={() => {
if (!this.state.submitDisabled) {
this.postEvent();
}
}}/>
</MaterialHeaderButtons>
),
});
However, the color change statement based on the value of this.state.submitDisabled did not work. I've checked its value, when this.state.submitDisabled is changed, the color of the button does not change. It seems that the color is determined when set as navigation params as above and will not change since then.
How can I achieve the effect of what I described?
when ever you change value of state also change navigation param too.see example
export class Example extends Component {
static navigationOptions = ({ navigation }) => {
const showModal = get(navigation, 'state.params.showModal');
return {
headerTitle: <Header
showModal={showModal}
backIcon />,
headerStyle: HeaderStyle,
headerLeft: null,
};
};
constructor(props) {
super(props);
this.state = {
showModal: false,
};
}
componentDidMount = () => {
this.props.navigation.setParams({
showModal: this.state.showModal,
});
}
handleModal=(value)=>{
this.setState({showModal:value});
this.props.navigation.setParams({
showModal: this.state.showModal,
});
}
render() {
return null;
}
}
In your implementation this.state.submitDisabled is not bound to the screen. Try the following:
static navigationOptions = ({ navigation }) => {
headerRight: (
<MaterialHeaderButtons>
<Item
title="submit"
iconName="check"
color={navigation.getParam('submitDisabled') ? colors.white_disabled : colors.white}
onPress={navigation.getParam('handlePress')}
/>
</MaterialHeaderButtons>
),
})
componentWillMount() {
this.props.navigation.setParams({
submitDisabled: this.state.submitDisabled,
handlePress: () => {
if (!this.state.submitDisabled) {
this.postEvent();
}
}
});
}
How can I hide and then show header(stack navigator) by pressing a button ?
static navigationOptions = ({ navigation }) => {
return {
header : null
}
}
This code set header to null and hide header but i can't show it again.
You can try something like this
static navigationOptions = {
headerVisible: this.state.headerVisible,
};
And In the constructor Initialise the state by
this.state = {headerVisible: true}
And on the buttonPress You can change the state by
<Button onPress={() => this.setState({headerVisible: !this.state.headerVisible})} />
Do you could try this?
this.state={
header: null
}
static navigationOptions = {
header: this.state.header,
};
...
headerfunc(){
if(this.state.header === null){
this.setSate({
header: ""
});
}else{
this.setSate({
header: null
});
}
}
...
<Button onPress={() => this.headerfunc()} />
You can add a custom function to handle the header visibility.
Create a handler function in your screen component,
toggleHeader=()=>{
let currentVal = this.props.navigation.getParam('isHeaderVisible', true);
this.props.navigation.setParams({ isHeaderVisible: !currentVal });
}
Add this handler function to your Button
render(){
...
<Button onPress={() => this.toggleHeader()} />
...
}
Finally, add static navigationOptions in your screen,
static navigationOptions = ({navigation}) => {
let headerVisible = navigation.getParam('isHeaderVisible',true);
return {
headerVisible
}
}
Thanks for every one. headerVisible property doesn't work.
There is another property called headerMode, it just works in stack navigator's config and we can't change it in our screen :
const StackNaviagtor = createStackNavigator({
showScreen: {
screen: MyScreen
}
}, {
headerMode: 'none'
})
only header property in navigationOption work and we can change it in our screen
the solution is :
import { Header } from "react-navigation";
static navigationOptions = ({ navigation }) => {
return {
header: navigation.getParam('isFullscreen') ? null : (headerProps) => <Header {...headerProps} />
}
and then:
render() {
let isFullscreen = this.props.navigation.getParam('isFullscreen');
return (
<Button title='Full Screen' onPress={() => { this.props.navigation.setParams({ isFullscreen: !isFullscreen }) }} />
)
I'm trying to pass params into a new screen, and implemented it like mentioned here.
I have the following TouchableOpacity button.
<TouchableOpacity
onPress={() => {
this.props.navigation.navigate('SomeScreen', {
title: 'Title',
subTitle: 'Subtitle',
});
}}
>
On the other page (let's call it Somescreen), I have the following:
render() {
const { navigation } = this.props;
const title = navigation.getParam('title');
}
But title above is undefined:
{ params: undefined, routeName: "Somescreen", key: "id-xx" }
My rootStack:
const RootStack = createStackNavigator({
SomescreenA: { screen: SomescreenA },
SomescreenB: { screen: SomescreenB },
}, { headerMode: 'none' });
Why are my params undefined in a new screen?
If you face a situation where your target screen get undefined params, probably you have a nested navigation stack.
Here you have to pass params to the navigate method in this way:
navigation.navigate('Root', {
screen: 'Settings',
params: { user: 'jane' },
});
For more information read this page in the official docs:
https://reactnavigation.org/docs/nesting-navigators/#navigating-to-a-screen-in-a-nested-navigator
In my specific case, I was calling a nested navigator, so I had to manage how send those params to their specific screen, so I did this:
Send params this way...the regular way:
navigation.navigate(
'OrderNavigator',
{itemSelected},
);
Then, from navigator stack I did this:
const OrderNavigator = ({route: {params}}) => {
return (
<Stack.Navigator initialRouteName="Order">
<Stack.Screen name="Order" component={Order} options={{headerShown: false}} initialParams={params} />
</Stack.Navigator>
);
};
And that's it. Then from the screen I got them like this:
const Order = ({route}) => {
const {itemSelected} = route.params;
const {first_name, last_name} = itemSelected;
return (...)
}
I've, unfortunately, encountered cases where navigate(route, params, ...) wouldn't pass the params object, just like you did.
As a workaround, I use the other variant - navigate({routeName, params, action, key}) that you can find here. It always works.
The accepted answer workaround did not work for me, so apparently if you use children to render your component (in screen options) and pass route as a prop, it works
if you are on react navigation v6^ use the useRoute hook to access the params object
const route = useRoute();
useRoute is a hook that gives access to the route object. It's useful when you cannot pass the route prop into the component directly, or don't want to pass it in case of a deeply nested child.
below is an implementation of this
import { useNavigation, useRoute } from '#react-navigation/native';
import { Pressable, Text } from 'react-native';
function Screen1() {
const navigation = useNavigation();
return (
<Pressable
onPress={() => {
navigation.navigate('Screen2', { caption: 'hey' });
}}
>
<Text> Go to Screen 2 </Text>
</Pressable>
);
}
function Screen2() {
const route = useRoute();
return <Text>{route.params.caption}</Text>;
}
how can i call a static function (static navigationOptions) from any other function in react native?
It fails when using the this keyword, but is it possible to render static navigationOptions again by calling it?
If you want to change navigation options dynamically, try like this
static navigationOptions = ({ navigation }) => {
return {
title: navigation.getParam('otherParam', 'A Nested Details Screen'),
};
};
this.props.navigation.setParams({otherParam: 'Updated!'})
Another method
static navigationOptions = ({ navigation }) => {
const {state} = navigation;
return {
title: `${state.params.title}`,
};
};
ChangeThisTitle = (titleText) => {
const {setParams} = this.props.navigation;
setParams({ title: titleText })
}
call this.ChangeThisTitle('your title') wherever you want
They only way to achieve it is using navigation params. So set your headerleft property flag or value using setParams. That will solve the issue. Below mentioned code should be used in your class.
static navigationOptions = ({ navigation }) => {
const { params = {} } = navigation.state;
let buttonView =
<TouchableOpacity style={navItemStyle} activeOpacity={0.7} onPress={() => { params.logoutClick() }}>
<Text style={navItemTxt}> Logout</Text>
</TouchableOpacity >
return {
title: 'Home',
headerLeft: params.showHeaderLeft && buttonView
};
};
componentDidMount() {
this.props.navigation.setParams({
showHeaderLeft: this.props.headerFlag,
//headerFlag used above is your value and showHeaderLeft is the name of the param
});
}