withNavigation can only be used on a view hierarchy of a navigator
Solution 1
In react-navigation, the main StackNavigator creates a context provider and the navigation
prop will be made available to any component below its level in the component tree if they use the context consumer.
Two ways to access the navigation
prop using context consumer is to either add the component to the StackNavigator, or use the withNavigation
function. However because of how React's context API works, any component that uses withNavigation
function must be below the StackNavigator in the component tree.
If you still want to access the navigation
prop regardless of the position in component tree, you will have to store the ref to the StackNavigator in a module. Following guide from react-navigation will help you do that https://reactnavigation.org/docs/en/navigating-without-navigation-prop.html
Solution 2
If you are using react-navigation version 5, use useNavigation hook with a functional component. This hook injects the navigation object into the functional component. Here is the link to the docs:
https://reactnavigation.org/docs/use-navigation/
Related videos on Youtube
Comments
-
BeniaminoBaggins almost 2 years
I'm getting the error:
Invariant Violation: withNavigation can only be used on a view hierarchy of a navigator. The wrapped component is unable to get access to navigation from props or context
I don't know why, because I'm using
withNavigation
in other components in my app and it works. I don't see a difference in the components that it works on to the one that causes the error.Code:
the component:
const mapStateToProps = (state: State): Object => ({ alertModal: state.formControls.alertModal }) const mapDispatchToProps = (dispatch: Dispatch<*>): Object => { return bindActionCreators( { updateAlertModalHeight: updateAlertModalHeight, updateAlertModalIsOpen: updateAlertModalIsOpen, updateHasYesNo: updateAlertModalHasYesNo }, dispatch ) } class AlertModalView extends Component<AlertModalProps, State> { render(): Node { return ( <View style={alertModalStyle.container}> <PresentationalModal style={presentationalModalStyle} isOpen={this.props.alertModal.isOpen} title={this.props.alertModal.title} navigation={this.props.navigation} updateHasYesNo={this.props.updateHasYesNo} message={this.props.alertModal.message} updateAlertModalHeight={this.props.updateAlertModalHeight} viewHeight={this.props.alertModal.viewHeight} hasYesNo={this.props.alertModal.hasYesNo} yesClicked={this.props.alertModal.yesClicked} updateAlertModalIsOpen={this.props.updateAlertModalIsOpen} /> </View> ) } } // $FlowFixMe const AlertModalViewComponent = connect( mapStateToProps, mapDispatchToProps )(AlertModalView) export default withNavigation(AlertModalViewComponent)
the stackNavigator:
import React from 'react' import { View, SafeAreaView } from 'react-native' import Icon from 'react-native-vector-icons/EvilIcons' import Add from '../product/add/view' import Login from '../user/login/view' import Search from '../product/search/query/view' import { Image } from 'react-native' import { StackNavigator, DrawerNavigator, DrawerItems } from 'react-navigation' const AddMenuIcon = ({ navigate }) => ( <View> <Icon name="plus" size={30} color="#FFF" onPress={() => navigate('DrawerOpen')} /> </View> ) const SearchMenuIcon = ({ navigate }) => ( <Icon name="search" size={30} color="#FFF" onPress={() => navigate('DrawerOpen')} /> ) const Stack = { Login: { screen: Login }, Search: { screen: Search }, Add: { screen: Add } } const DrawerRoutes = { Login: { name: 'Login', screen: Login }, 'Search Vegan': { name: 'Search', screen: StackNavigator(Stack.Search, { headerMode: 'none' }), navigationOptions: ({ navigation }) => ({ drawerIcon: SearchMenuIcon(navigation) }) }, 'Add vegan': { name: 'Add', screen: StackNavigator(Stack.Add, { headerMode: 'none' }), navigationOptions: ({ navigation }) => ({ drawerIcon: AddMenuIcon(navigation) }) } } const CustomDrawerContentComponent = props => ( <SafeAreaView style={{ flex: 1, backgroundColor: '#3f3f3f', color: 'white' }}> <View> <Image style={{ marginLeft: 20, marginBottom: 0, marginTop: 0, width: 100, height: 100, resizeMode: 'contain' }} square source={require('../../images/logo_v_white.png')} /> </View> <DrawerItems {...props} /> </SafeAreaView> ) const Menu = StackNavigator( { Drawer: { name: 'Drawer', screen: DrawerNavigator(DrawerRoutes, { initialRouteName: 'Login', drawerPosition: 'left', contentComponent: CustomDrawerContentComponent, contentOptions: { activeTintColor: '#27a562', inactiveTintColor: 'white', activeBackgroundColor: '#3a3a3a' } }) } }, { headerMode: 'none', initialRouteName: 'Drawer' } ) export default Menu
Here I render the
StackNavigator
which isMenu
in my app component:import React, { Component } from 'react' import Menu from './menu/view' import Props from 'prop-types' import { Container } from 'native-base' import { updateAlertModalIsOpen } from './formControls/alertModal/action' import AlertModalComponent from './formControls/alertModal/view' import UserLoginModal from './user/login/loginModal/view' class Vepo extends Component { componentDidMount() { const { store } = this.context this.unsubscribe = store.subscribe(() => {}) store.dispatch(this.props.fetchUserGeoCoords()) store.dispatch(this.props.fetchSearchQueryPageCategories()) store.dispatch(this.props.fetchCategories()) } componentWillUnmount() { this.unsubscribe() } render(): Object { return ( <Container> <Menu store={this.context} /> <AlertModalComponent yesClicked={() => { updateAlertModalIsOpen(false) }} /> <UserLoginModal /> </Container> ) } } Vepo.contextTypes = { store: Props.object } export default Vepo
and my root component:
export const store = createStore( rootReducer, vepo, composeWithDevTools(applyMiddleware(createEpicMiddleware(rootEpic))) ) import NavigationService from './navigationService' export const App = () => ( <Provider store={store}> <Vepo fetchUserGeoCoords={fetchUserGeoCoords} fetchSearchQueryPageCategories={fetchSearchQueryPageCategories} fetchCategories={fetchCategories} /> </Provider> ) AppRegistry.registerComponent('vepo', () => App)
I have changed my Vepo component to this to implement the answer by vahissan:
import React, { Component } from 'react' import Menu from './menu/view' import Props from 'prop-types' import { Container } from 'native-base' import { updateAlertModalIsOpen } from './formControls/alertModal/action' import AlertModalComponent from './formControls/alertModal/view' import UserLoginModal from './user/login/loginModal/view' import NavigationService from './navigationService' class Vepo extends Component { componentDidMount() { const { store } = this.context this.unsubscribe = store.subscribe(() => {}) store.dispatch(this.props.fetchUserGeoCoords()) store.dispatch(this.props.fetchSearchQueryPageCategories()) store.dispatch(this.props.fetchCategories()) } componentWillUnmount() { this.unsubscribe() } render(): Object { return ( <Container> <Menu store={this.context} ref={navigatorRef => { NavigationService.setTopLevelNavigator(navigatorRef) }}> <AlertModalComponent yesClicked={() => { updateAlertModalIsOpen(false) }} /> </Menu> <UserLoginModal /> </Container> ) } } Vepo.contextTypes = { store: Props.object } export default Vepo
No errors, but the alertModal no longer displays
-
kwoxer over 3 yearsWorked perfectly, thanks. Was heavily searching for this.
-
Casey L over 3 yearsThank you! Despite having a class component as a deeply nested child of my one of my StackNavigator screens (per @vahissan's suggestion), I could not get past this error using
withNavigation
. I changed my class component to a functional component, and used theuseNavigation
hook with no issues. Did not have to move anything around in my component tree.