Like so:
I'm running react-navigation v4.
First you have to follow the tutorial on how to set up a react-navigation modal, the one that has a jump animation and doesn't look like the native formSheet. You have to set up a stack navigator with your root navigator as one child and the modal as another:
And it scales, because you can have more than one of these modals as children.
The code for this is the following:
const RootNavigator = createStackNavigator(
{
index: { screen: AppNavigator },
[NC.SCREEN_ROOM_INFO]: { screen: RoomInfoNavigator },
[NC.SCREEN_CHAT_CREATE]: { screen: RoomCreateNavigator },
[NC.SCREEN_CHAT_SEARCH]: { screen: ChatSearchNavigator },
[NC.SCREEN_CHAT_GIF_PICKER]: { screen: GifPickerNavigator }
},
{
mode: 'modal',
headerMode: 'none',
transitionConfig: () => ({
transitionSpec: {
duration: 0
}
}),
transparentCard: true
}
)
Then you need to implement these, from my example, 4 navigators that will be displayed as modals each like so:
// Here you'll specify the screens you'll navigate in this modal, starting from index.
const RoomInfoStack = createStackNavigator({
index: { screen: NavigableRoomInfo },
[NC.SCREEN_ROOM_ROSTER]: { screen: NavigableRoomRoster },
[NC.SCREEN_ROOM_NOTIFICATION_PREFERENCES]: { screen: NavigableRoomNotificationPreferences },
[NC.SCREEN_ROOM_EDIT]: { screen: NavigableRoomEdit }
})
type NavigationComponent<T = any> = {
navigation?: NavigationScreenProp<NavigationState, T>
}
type Props = NavigationComponent
// THIS code is from the react-navigation tutorial on how to make a react-navigation modal:
// https://reactnavigation.org/docs/4.x/custom-navigators/#extending-navigators
class RoomInfoNavigator extends React.Component<Props> {
static router = {
...RoomInfoStack.router,
getStateForAction: (action, lastState) => {
// check for custom actions and return a different navigation state.
return RoomInfoStack.router.getStateForAction(action, lastState)
}
}
constructor(props) {
super(props)
this.onClose = this.onClose.bind(this)
}
onClose() {
this.props.navigation?.goBack()
}
// And here is the trick, you'll render an always open RN formSheet
// and link its dismiss callbacks to the goBack action in react-navigation
// and render your stack as its children, redirecting the navigator var to it in props.
render() {
return (
<Modal
visible={true}
animationType={'slide'}
supportedOrientations={['portrait', 'landscape']}
presentationStyle={'formSheet'}
onRequestClose={() => this.onClose()}
onDismiss={() => this.onClose()}
>
<RoomInfoStack {...this.props} />
</Modal>
)
}
}
export { RoomInfoNavigator }
This export is what our root stack imported before. Then you just need to render the screens, I have a pattern that I do to extract the navigation params to props in case this screen is ever displayed without navigation:
const NavigableRoomInfo = (props: NavigationComponent<RoomInfoProps>) => {
const roomInfo = props.navigation!.state!.params!.roomInfo
const roomInfoFromStore = useRoomFromStore(roomInfo.id)
// Here I update the navigation params so the navigation bar also updates.
useEffect(() => {
props.navigation?.setParams({
roomInfo: roomInfoFromStore
})
}, [roomInfoFromStore])
// You can even specify a different Status bar color in case it's seen through modal view:
return (
<>
<StatusBar barStyle="default" />
<RoomInfo roomInfo={roomInfoFromStore} navigation={props.navigation} />
</>
)
}
Sources:
https://reactnavigation.org/docs/4.x/modal
https://reactnavigation.org/docs/4.x/custom-navigators/#extending-navigators
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();
}
}
});
}
I'm using react-navigation's SwitchNavigator to manage my navigation based on authentication state. When Authenticated, I want to use Redux to store data I'm fetching.
My SwitchNavigator looks like this
SwitchRouteConfig = {
AuthLoading: AuthLoadingScreen,
Authenticated: AppStackNavigator,
NotAuthenticated: AuthStackNavigator,
}
SwitchConfig = {
initialRouteName: 'AuthLoading',
}
export default createSwitchNavigator(SwitchRouteConfig, SwitchConfig);
My Authenticated navigation looks like this:
// App Bottom Tab Navigator
const AppTabRouteConfig = {
AddItem: { screen: AddItem },
Items: { screen: Items },
Map: { screen: Map },
Help: { screen: Help },
}
const AppTabConfig = { initialRouteName: 'Items',}
const AppTabNavigator = new createBottomTabNavigator(
AppTabRouteConfig,
AppTabConfig)
And in my Screen we have:
class Items extends React.Component {
constructor(props){
super(props);
this.state = {};
}
componentDidMount(){
this.props.getData(); //call our redux action
}
render() {
if(this.props.isLoading){
return(
<View>
<ActivityIndicator />
</View>
)
} else {
return (
<Provider store={store}>
<SafeAreaView>
<FlatList
data={this.props.dataSource.features}
renderItem={({ item }) =>
<TouchableWithoutFeedback>
<View style={styles.listContainer}>
<Text>{item.prop1}</Text>
<Text>{item.prop2}</Text>
</View>
</TouchableWithoutFeedback>
}
/>
</SafeAreaView>
</Provider>
)
}
}
}
function mapStateToProps(state, props) {
return {
isLoading: state.dataReducer.isLoading,
dataSource: state.dataReducer.dataSource
}
}
function mapDispatchToProps(dispatch) {
return bindActionCreators(Actions, dispatch);
}
export default connect(mapStateToProps,
mapDispatchToProps)(Items)
When I'm not authenticated, that works fine. I can login. When I am authenticated, I get the following error:
Invariant Violation: Could not find "store"
in either the context or props of
Connect(Items)". Either wrap the root
component in a <Provider>, or explicitly pass
"store" as a prop to "Connect(Items)".
In the reading I've done today, all the samples have a single top-level component which they wrap with . So, I'm not understanding how you instantiate the store and manage Redux without that model.
I should mention two additional things:
The initial authenticated app screen worked fine before I started to implement Redux.
I'm not trying to manage the state with Redux, just application data.
Project started with Create-React-Native-App.
Thank you!
You have to wrap the root component (the switch navigator) inside Provider to make the store available to all container components.
const SwitchRouteConfig = {
AuthLoading: AuthLoadingScreen,
Authenticated: AppStackNavigator,
NotAuthenticated: AuthStackNavigator,
}
const SwitchConfig = {
initialRouteName: 'AuthLoading',
}
const Navigator = createSwitchNavigator(SwitchRouteConfig, SwitchConfig);
// You can use a stateless component here if you want
export default class Root extends React.PureComponent {
render() {
return (
<Provider store={store}>
<Navigator />
</Provider >
);
}
}
The Goal
Using react navigation, navigate from a screen in a navigator to a screen in a different navigator.
More Detail
If I have the following Navigator structure:
Parent Navigator
Nested Navigator 1
screen A
screen B
Nested Navigator 2
screen C
screen D
how do I go from screen D under nested navigator 2, to screen A under nested navigator 1? Right now if I try to navigation.navigate to screen A from screen D there will be an error that says it doesn't know about a screen A, only screen C and D.
I know this has been asked in various forms in various places on this site as well as GitHub(https://github.com/react-navigation/react-navigation/issues/983, https://github.com/react-navigation/react-navigation/issues/335#issuecomment-280686611) but for something so basic, there is a lack of clear answers and scrolling through hundreds of GitHub comments searching for a solution isn't great.
Maybe this question can codify how to do this for everyone who's hitting this very common problem.
In React Navigation 5, this becomes much easier by passing in the screen as the second parameter:
navigation.navigate('Nested Navigator 2', { screen: 'screen D' });
You can also include additional levels if you have deeply nested screens:
navigation.navigate('Nested Navigator 2', {
screen: 'Nested Navigator 3', params: {
screen: 'screen E'
}
});
Update: For React Navigation v5, see #mahi-man's answer.
You can use the third parameter of navigate to specify sub actions.
For example, if you want to go from screen D under nested navigator 2, to screen A under nested navigator 1:
this.props.navigation.navigate(
'NestedNavigator1',
{},
NavigationActions.navigate({
routeName: 'screenB'
})
)
Check also:
https://reactnavigation.org/docs/nesting-navigators/
React Navigation v3:
Navigation.navigate now takes one object as the parameter. You set the stack name then navigate to the route within that stack as follows...
navigation.navigate(NavigationActions.navigate({
routeName: 'YOUR_STACK',
action: NavigationActions.navigate({ routeName: 'YOUR_STACK-subRoute' })
}))
Where 'YOUR_STACK' is whatever your stack is called when you create it...
YOUR_STACK: createStackNavigator({ subRoute: ... })
On React Navigation v5 you have here all the explanation:
https://reactnavigation.org/docs/nesting-navigators#navigating-to-a-screen-in-a-nested-navigator
Route definition
function Root() {
return (
<Stack.Navigator>
<Stack.Screen name="Profile" component={Profile} />
<Stack.Screen name="Settings" component={Settings} />
</Stack.Navigator>
);
}
function App() {
return (
<NavigationContainer>
<Drawer.Navigator>
<Drawer.Screen name="Home" component={Home} />
<Drawer.Screen name="Root" component={Root} />
</Drawer.Navigator>
</NavigationContainer>
);
}
Instruction
navigation.navigate('Root', { screen: 'Settings' });
In React Navigation v5, you can do something like:
navigation.navigate('Root', {
screen: 'Settings',
params: {
screen: 'Sound',
params: {
screen: 'Media',
},
},
});
In the above case, you're navigating to the Media screen, which is in a navigator nested inside the Sound screen, which is in a navigator nested inside the Settings screen.
In React Navigation v5/v6
Navigation to Specific Screen on a Stack
navigation.navigate('Home', {
screen: 'Profile',
params: {userID: 1}
}
)
What If We Nest More?
Consider this structure:
NAVIGATOR:
*StackA
*ScreenC
*ScreenD
*StackB
*ScreenI
*StackE
*ScreenF
*StackG
*ScreenJ
*ScreenH
We want to get from ScreenC inside StackA all the way to ScreenH in StackB.
We can actually chain the parameters together to access specific screens.
navigation.navigate('StackB',{
screen: 'StackE',
params: {
screen: 'StackG',
params: {
screen: 'ScreenH'
}
}
}
)
For more information
In React Navigation 3
#ZenVentzi, Here is the answer for multi-level nested navigators when Nested Navigator 1 has Nested Navigator 1.1.
Parent Navigator
Nested Navigator 1
Nested Navigator 1.1
screen A
screen B
Nested Navigator 2
screen C
screen D
We can just inject NavigationActions.navigate() several times as needed.
const subNavigateAction = NavigationActions.navigate({
routeName: 'NestedNavigator1.1',
action: NavigationActions.navigate({
routeName: 'ScreenB',
params: {},
}),
});
const navigateAction = NavigationActions.navigate({
routeName: 'NestedNavigator1',
action: subNavigateAction,
});
this.props.navigation.dispatch(navigateAction);
UPDATE
For React Navigation 5, please check #mahi-man's answer above.
https://stackoverflow.com/a/60556168/10898950
React Navigation v6
docs
function Home() {
return (
<Tab.Navigator>
<Tab.Screen name="Feed" component={Feed} />
<Tab.Screen name="Messages" component={Messages} />
</Tab.Navigator>
);
}
function App() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
name="Home"
component={Home}
options={{ headerShown: false }}
/>
<Stack.Screen name="Profile" component={Profile} />
<Stack.Screen name="Settings" component={Settings} />
</Stack.Navigator>
</NavigationContainer>
);
}
navigation.navigate('Home', { screen: 'Messages' });
try this,
Parent Navigator
Nested Navigator 1
screen A
screen B
Nested Navigator 2
screen A
screen C
screen D
and then, there is no need to go A in 1 from D in 2, you can just go A from D both in 2, you can check here image or A stack navigator for each tab
Complete freedom: singleton w/ navigationOptions
If you have a situation where you have multiple navigation stacks and sub stacks, this can be frustrating to know how to get a reference to the desired stack given how React Navigation is setup. If you were simply able to reference any particular stack at any given time, this would be much easier. Here's how.
Create a singleton that is specific to the stack you want to reference anywhere.
// drawerNavigator.js . (or stackWhatever.js)
const nav = {}
export default {
setRef: ref => nav.ref = ref,
getRef: () => nav.ref
}
Set the reference on desired navigator using navigatorOptions
import { createBottomTabNavigator } from 'react-navigation'
import drawerNavigation from '../drawerNavigator'
const TabNavigation = createBottomTabNavigator(
{
// screens listed here
},
{
navigationOptions: ({ navigation }) => {
// !!! secret sauce set a reference to parent
drawerNavigation.setRef(navigation)
return {
// put navigation options
}
}
}
)
Now you can reference drawerNavigator anywhere inside or outside
// screen.js
import drawerNavigator from '../drawerNavigator'
export default class Screen extends React.Component {
render() {
return (
<View>
<TouchableHighlight onPress={() => drawerNavigator.getRef().openDrawer()}>
<Text>Open Drawer</Text>
</TouchableHighlight>
</View>
)
}
}
Explanation
Within Step 2, a Tab Navigator is one of the screens within a Drawer Navigator. Tab Navigator needs to close the drawer but also anywhere within your app, you can call drawerNavigator.getRef().closeDrawer() after this step is performed. You are not limited to having direct access to props.navigation after that step.
If nothing else works (as in my case), just do:
Main/Root/App.js:
<StackNavigator ref={(x) => (global.stackNavigator = x)} />
Anywhere:
global.stackNavigator.dispatch(
NavigationActions.navigate({
routeName: 'Player',
params: { },
}),
);
In React Navigation V5, you can do like this:
but remember to you placing this on the parent side
this.props.navigation.navigate(
'Nested Navigator 1',
{name: 'jane'},
this.props.navigation.navigate('Screen A', {id: 2219}),
);
While working on a react-native project, i came across same situation. I have tried multiple ways in navigating to screen but failed.
After many trials, I tried passing parents navigation object to children and made a navigation function call and it worked.
Now coming to your issues, If you want to navigation from screen D to screen A do follow these steps.
-> Pass nested navigator 2 navigation props to its children using screenProps.
export default class Home extends Component {
static navigationOptions = {
header:null
};
constructor(props) {
super(props);
this.state = {
profileData: this.props.navigation.state.params,
route_index: '',
}
}
render() {
return (
<ParentNavigator screenProps={this.props.navigation} />
);
}
}
export const ParentNavigator = StackNavigator({
// ScreenName : { screen : importedClassname }
Nested1: { screen: nested1 },
Nested2: { screen : nestes1 }
});
export const nested1 = StackNavigator({
ScreenA: { screen: screenA },
ScreenB: { screen : screenB }
});
export const nested2 = StackNavigator({
ScreenC: { screen: screenC },
ScreenD: { screen : screenD }
});
You can receive the navigation in children using
const {navigate} = this.props.screenProps.navigation;
Now this navigate() can be used to navigate between children.
I accept that this process is little confusing but i couldn't find any solutions so had to go with this as i have to complete my requirement.
I've found also such solution here:
onPress={() =>
Promise.all([
navigation.dispatch(
NavigationActions.reset({
index: 0,
// TabNav is a TabNavigator nested in a StackNavigator
actions: [NavigationActions.navigate({ routeName: 'TabNav' })]
})
)
]).then(() => navigation.navigate('specificScreen'))
}
const subAction = NavigationActions.navigate({ routeName: 'SignInScreen' });
AsyncStorage.clear().then(() =>
this.props.navigation.navigate('LoggedOut', {}, subAction));
LoggedOut is the stack name where signIn screen is placed.
My goal was to have the authentication screens all share the same background and the rest of the app using the regular stack transition.
After hours I've found the solution is to have the createStackNavigator() in the same file as your component wrapper. So that you can successfully expose the static router as the document stated. This will avoid the You should only render one navigator explicitly in your app warning and you can use this.props.navigation.navigate('AnyScreen') to navigate to any nested screen.
AuthRouter.js
export const AuthNavigationStack = createStackNavigator(
{
Login: {
screen: Login
},
CreateAccount: {
screen: CreateAccount
}
}
);
export default class AuthContainer extends React.Component {
constructor( props ) {
super( props );
}
static router = AuthNavigationStack.router;
render() {
return (
<ImageBackground
style={ {
width: '100%',
height: '100%'
} }
source={ require( '../Images/johannes-andersson-yosimite.jpg' ) }
blurRadius={ 10 }
>
<StatusBar
barStyle="dark-content"
backgroundColor="transparent"
translucent={ true }
/>
<AuthNavigationStack navigation={ this.props.navigation } />
</ImageBackground>
);
}
}
MessengerRouter.js
export const MessengerStackNavigator = createStackNavigator(
{
Chat: {
screen: Chat,
},
User: {
screen: User,
},
}
);
export default class MainContainer extends React.Component {
constructor( props ) {
super( props );
}
static router = MessengerStackNavigator.router;
render() {
return <MessengerStackNavigator navigation={ this.props.navigation } />;
}
}
Router.js
import { createStackNavigator } from 'react-navigation';
import AuthRouter from './AuthRouter';
import MessengerRouter from './MessengerRouter';
export const RootNavigationStack = createStackNavigator( {
AuthContainer: {
screen: AuthRouter,
navigationOptions: () => ( {
header: null
} )
},
MessengerRouter: {
screen: MessengerRouter,
navigationOptions: () => ( {
header: null
} )
}
} );
RootContainer.js
import { RootNavigationStack } from '../Config/Router';
class RootContainer extends Component {
render() {
return <RootNavigationStack />;
}
}
Notes:
Pass header: null from the RootNaviagtionStack to the nested stacks to remove the overlapping header
If you navigate from Nested A to Nested B and use the back button, it will return you to the first screen in Nested B. Not a big problem but I haven't figured out how to fix it.
This is another way to navigate to nested screen using Version: 5.x. It worked without any additional configuration. More info here: https://reactnavigation.org/docs/use-link-to
const linkTo = useLinkTo();
// ...
// Just call this
linkTo(`/main/sub/subB`);
I'm using react-navigation and here is my structure :
The root stack navigator :
export const Root = StackNavigator({
Index: {
screen: Index,
navigationOptions: ({ navigation }) => ({
}),
},
Cart: {
screen: Cart,
navigationOptions: ({ navigation }) => ({
title: 'Votre panier',
drawerLabel: 'Cart',
drawerIcon: ({ tintColor }) => <Icon theme={{ iconFamily: 'FontAwesome' }} size={26} name="shopping-basket" color={tintColor} />
}),
},
...
My structure looks like this :
StackNavigator (Root)
DrawerNavigator (Index)
TabNavigator
MyPage
MyPage (same page formatted with different datas)
...
So my question is, where do I load my data, initialize my application ? I need somewhere called once, called before the others pages.
The first page displayed in my application is the MyPage page. But as you can see, because of the TabNavigator, if I put my functions inside, it will be called many times.
Some will says in the splashscreen, but I'm using the main splashscreen component and I don't have many controls over it.
I thought about my App.js where we create the provider, but I don't think this is a good idea ?
const MyApp = () => {
//TODO We're loading the data here, I don't know if it's the good decision
ApplicationManager.loadData(store);
SplashScreen.hide();
return (
<Provider store={store}>
<Root/>
</Provider>
);
};
What is the good way to do it ?
class MyApp extends Component {
state = {
initialized: false
}
componentWillMount() {
// if this is a promise, otherwise pass a callback to call when it's done
ApplicationManager.loadData(store).then(() => {
this.setState({ initialized: true })
})
}
render() {
const { initialized } = this.state
if (!initialized) {
return <SplashScreen />
}
return (
<Provider store={store} >
<Root />
</Provider>
);
}
}
TabNavigator by default renders/loads all its child components at the same time, but if you set property lazy: true components will render only if you navigate. Which means your functions will not be called many times.
const Tabs = TabNavigator(
{
MyPage : {
screen: MyPage
},
MyPage2 : {
screen: MyPage,
}
}
},
{
lazy: true
}
);
If you use this structure and call fetching data inside of MyPage you can add logic in componentWillReceiveProps that will check is data already in store and/or is it changed before fetching new data. Calling your fetch functions from MyPage gives you the ability to pull fresh data on every page/screen visit or do "pull to refresh" if you need one.
You could also pull initial data in splashscreen time, I would just not recommend pulling all your app data, data for all screens, at that time since you probably don't need it all at once. You can do something like:
class MyApp extends Component {
state = {
initialized: false
}
componentWillMount() {
// if this is a promise, otherwise pass a callback to call when it's done
ApplicationManager.loadData(store).then(() => {
this.setState({ initialized: true })
})
}
render() {
const { initialized } = this.state
if (!initialized) {
return null
}
return (
<Provider store={store} >
<Root />
</Provider>
);
}
}
class Root extends Component {
componentDidMount() {
SplashScreen.hide();
}
...
}
You should do it in App.js or where you initialize your StackNavigator. If I were you, I would put a loading screen, which would get replaced by the StackNavigator structure once the data is ready.
I wouldn't do it in the App because you lose control. Sadly I haven't used react-navigation or redux but I see that the TabNavigator has a tabBarOnPress method, which I would use to trigger the loading. You can load every page data on demand.
https://reactnavigation.org/docs/navigators/tab#tabBarOnPress