What am i doing wrong with this stack navigator? - react-native

I am trying to teach myself react-native by building a new app, using Firebase as the backend. Currently I'm trying to get my head around react-navigation navigators to lay out the screens on my app before i add content.
The issue I'm having is that the stack navigator doesn't navigate. It renders the next screen, and adds it to the page, and then initiates the transition - but for whatever reason the transition just doesn't happen.
so the following events fire:
* willBlur (on the originating screen)
*onTransitionStart` (on the stack transition)
I've assumed i've done something wrong, so i've rewritten three or four times, and this final time i've boiled it down to the simplest possible iteration of the problem, just a stack navigator and two screens - and i'm getting the same issue.
I've gone through the documentation a few times, and i think at this point i've just been a massive idiot about this.
This is my entire app to show it
import React from 'react';
import { Text, TouchableOpacity } from 'react-native';
import { createAppContainer, createStackNavigator } from 'react-navigation';
class Main extends React.Component {
navigateToList() {
const { navigation } = this.props;
navigation.navigate('list');
}
render() {
return (
<TouchableOpacity
onPress={this.navigateToList.bind(this)}
>
<Text>Go To Listing Page</Text>
</TouchableOpacity>
);
}
}
class List extends React.Component {
render() {
const { refreshing } = this.props;
return (
<Text>LISTING PAGE</Text>
);
}
}
const MainStack = createStackNavigator({
mode: { screen: Main },
list: { screen: List }
}, {
initialRouteName: 'mode',
});
const App = createAppContainer(MainStack);
export default () => (
<App />
);
and my package.json has the following:
"react": "16.8.3",
"react-native": "0.59.3",
"react-native-firebase": "^5.4.0",
"react-native-gesture-handler": "^1.3.0",
"react-navigation": "^3.11.0"
At the moment this just sits showing the "main" screen, and doesn't navigate to the "list" screen. Also the button stops responding, so it looks like things start working but then never complete for some reason i can't explain.

Related

How can I build login and registration system in react native

I am making a login and registration system in react native. I have 13 screens in total out of which 4 screens needs person not to be logged in and in 4 screens people need to be logged in and the remaining 5 are independent of the logged in status.
I have successfully fetched the details into the application and stored it in AsyncStorage.I have then used two different types of approaches.
I have used the function 'check' [ tried this in render and component did mount and constructor ] mentioned below to perform whether the user data persists in the AsyncStorage and if exists, I have redirected the user to 'Home' screen.
This works however whenever , I go back to login page, I am not redirected.
I have made 3 different navigators one that is independent, one that requires logged in and one that requires not to be logged in. This creates a problem I am not able to navigate from one navigator to another navigator's specific screen.
let check = AsyncStorage.getItem('user').then(res => {
res = JSON.parse(res);
typeof res == 'string'
? alert(res)
: this.props.navigation.navigate('HomeScreen');
. });
I got two questions here.
1.Is there any way using which I can navigate from one navigator to a specific screen in another navigator ?
2.What is the best way to achieve my required result ?
With the help of createSwitchNavigator you can achieve this Auth Flow
Set up our navigators
import { createSwitchNavigator, createStackNavigator, createAppContainer } from 'react-navigation';
// Implementation of HomeScreen, OtherScreen, SignInScreen, AuthLoadingScreen
// goes here.
const AppStack = createStackNavigator({ Home: HomeScreen, Other: OtherScreen });
const AuthStack = createStackNavigator({ SignIn: SignInScreen });
export default createAppContainer(createSwitchNavigator(
{
AuthLoading: AuthLoadingScreen,
App: AppStack,
Auth: AuthStack,
},
{
initialRouteName: 'AuthLoading',
}
));
Implement our authentication loading screen
import React from 'react';
import {
ActivityIndicator,
AsyncStorage,
StatusBar,
StyleSheet,
View,
} from 'react-native';
class AuthLoadingScreen extends React.Component {
constructor(props) {
super(props);
this._bootstrapAsync();
}
// Fetch the token from storage then navigate to our appropriate place
_bootstrapAsync = async () => {
const userToken = await AsyncStorage.getItem('userToken');
// This will switch to the App screen or Auth screen and this loading
// screen will be unmounted and thrown away.
this.props.navigation.navigate(userToken ? 'App' : 'Auth');
};
// Render any loading content that you like here
render() {
return (
<View>
<ActivityIndicator />
<StatusBar barStyle="default" />
</View>
);
}
}

Snapchat-like Horizontal ScrollView Combined with React Navigation

I'm trying to create a ScrollView similar to Snapchat's frontend, with 3 screens. However, I want to be able to use react-navigation's ability to set an initial route so that every time the home screen loads, it starts on the middle screen instead of the left-most screen.
This is for react-native version: 0.57.1, and react-navigation version: 3.0.9. This works fine when I put all 3 screens in the ScrollView component directly, but then I can't find a way to select which screen is displayed initially.
Here is something similar to what I'm aiming for:
const HomeScreenStack = createStackNavigator({
Goals: GoalsScreen,
Dash: DashScreen,
Plans: PlansScreen,
}, {
initialRouteName: "Dash"
})
class HomeScreen extends React.Component {
render() {
return (
<ScrollView
horizontal={true}
pagingEnabled={true}
showsHorizontalScrollIndicator={false}
showsVerticalScrollIndicator={true}>
<HomeScreenStack />
</ScrollView>
)
}
}
I expect HomeScreenStack to display all 3 routes within a ScrollView, but I get an error saying:
Invariant Violation: Invariant Violation:
The navigation prop is missing for this navigator.
In react-navigation 3 you must set up your app container directly.
More info: https://reactnavigation.org/docs/en/app-containers.html
Rather than using a ScrollView, you could take advantage of react-navigation's createMaterialTopTabNavigator and nullify the tabBarComponent. It has swipe navigation enabled by default.
You also need to set up an app container for react-navigation, which is where that error is coming from.
Note that AppContainer should be used only once and in the main entry point file, such as App.js.
import { createAppContainer, createMaterialTopTabNavigator } from 'react-navigation'
const HomeNavigator = createMaterialTopTabNavigator(
{
Goals: GoalsScreen,
Dash: DashScreen,
Plans: PlansScreen,
},
{
initialRouteName: "Dash",
tabBarComponent: null
}
)
const AppContainer = createAppContainer(HomeNavigator)
class HomeScreen extends React.Component {
render() {
return (
<AppContainer />
)
}
}

React Native Open Tab Bar in Modal (using expo)

I'm trying to have one of the tab bar items open as modal when clicked, I'm currently using expo. I've read this: How do i make a TabNavigator button push a modal screen with React Navigation. However, I'm still learning React Native and I'm honestly not sure how to use this using expo navigation. Currently, I have created a stack navigator using "createStackNavigator" for each of the screens. And lastly, I have exported a bottom tab navigator including all of the stacks:
export default createBottomTabNavigator({
Tab1Stack,
Tab2Stack,
Tab3Stack,
Tab4Stack
});
I need Tab4 to open as a modal. Can someone people help me with this? Thank you in advance!!
Note this was built for "react-navigation": "3.3.0" so your mileage my vary for more recent versions of Expo and react-navigation.
To make a modal appear when you tap on the last tab in a TabNavigator requires you to nest your TabNavigator inside a StackNavigator.
So we could set up something like this:
#App.js
A simple App.js.
import React from 'react';
import AppContainer from './MainNavigation';
export default class App extends React.Component {
constructor (props) {
super(props);
this.state = {
};
}
render () {
return (
<AppContainer />
);
}
}
#MainNavigation.js
This file contains two navigators. A TabNavigator and a StackNavigator. The TabNavigator is nested inside the StackNavigator.
To be able to show the ModalScreen we have to override the tabBarOnPress function inside the defaultNavigationOptions which is inside the config for the TabNavigator.
We need to check the navigation.state.key to see where we are navigating too. If we are going to Tab3 we can intercept the call and navigate to the ModalScreen instead. Otherwise we use the defaultHandler and go to the tab that was tapped.
import Screen1 from './Screen1';
import Screen2 from './Screen2';
import Screen3 from './Screen3';
import ModalScreen from './ModalScreen';
import { createBottomTabNavigator, createAppContainer, createStackNavigator } from 'react-navigation';
const screens = {
Tab1: {
screen: Screen1
},
Tab2: {
screen: Screen2
},
Tab3: {
screen: Screen3
}
};
const config = {
headerMode: 'none',
initialRouteName: 'Tab1',
defaultNavigationOptions: {
tabBarOnPress: (data) => {
// this is where the magic happens
const { navigation, defaultHandler } = data;
// we check to see if the navigation key is going to be on Tab3
if (navigation.state.key === 'Tab3') {
// if it is we show the ModalScreen by navigating to it
navigation.navigate('ModalScreen');
} else {
// otherwise we call the defaultHandler and navigate to the tab we pressed
defaultHandler(navigation.state.key);
}
}
}
};
const TabNavigator = createBottomTabNavigator(screens, config);
const stackScreens = {
Tabs: {
screen: TabNavigator
},
ModalScreen: {
screen: ModalScreen
}
};
//we need to set the mode to be modal
const stackConfig = {
headerMode: 'none',
initialRouteName: 'Tabs',
mode: 'modal'
};
const MainNavigator = createStackNavigator(stackScreens, stackConfig);
export default createAppContainer(MainNavigator);
#Screen.js
A simple screen for each tab
import React from 'react';
import { View, StyleSheet, Text } from 'react-native';
export default class Screen extends React.Component {
render () {
return (
<View style={styles.container}>
<Text>Tab Screen</Text>
</View>
);
}
}
#ModalScreen
This screen is the modal that will appear when the tab for the third screen is tapped.
As it is part of the StackNavigator, defined above, it has access to the navigation prop. We set up a simple button that when pressed calls this.props.navigation.goBack() This will dismiss the modal.
import React from 'react';
import { View, StyleSheet, Text, Button } from 'react-native';
export default class Screen extends React.Component {
render () {
return (
<View style={styles.container}>
<Text>Modal Screen</Text>
<Button
title={'close modal'}
onPress={() => this.props.navigation.goBack()}
/>
</View>
);
}
}
Here is a snack with it working, https://snack.expo.io/#andypandy/show-modal-on-tab-press, hopefully it will show you how to set it up.

React Native - React Navigation slow transitions when nesting navigators

I am building a cross-platform native application using react-native and using react-navigation for navigating to and from screens and managing navigation state using redux. The problem arises when I am nesting my navigators.
For example, I am using Stack Navigator as the default navigator for my app.
export const DefaultNavigate = new StackNavigator(
{
Login: {
screen: LoginScreen,
},
Home: {
screen: AppDrawerNavigate,
},
AppTabNav: {
screen: AppTabNavigator,
},
}
);
where my first screen is loginscreen and home screen is a drawer navigator.
const AppDrawerNavigate = new DrawerNavigator(
{
InProcess: {
screen: InProcess,
},
Machine: {
screen: Machine
},
Settings: {
screen: Settings
},
Logout: {
screen: Logout
},
ContactUs: {
screen: ContactUs
}
}
);
When the user clicks on the Machine in the Drawer Navigator I am navigating the screen to AppTabNav declared in DefaultNavigator.
const AppTabNavigator = new TabNavigator(
{
MachineList: {
screen: MachineList,
},
CalendarView: {
screen: CalendarView,
}
},
);
which is a tab navigator with two screens as the name suggests one is using listview to display list and the other is using the calendarview to display calendar. There are around only 30-40 items in my dataSource of listview so rendering them is a piece of cake for listview. But when there is navigation from any screen to Machine screen from DrawerNavigator there is lag of 1-2sec and js thread drops to -2.1 which is really slowing down the transition.
and if someone need the code for Machine screen in drawer navigator here it is,
componentDidMount() {
if(this.state.loaded)
this.props.navigation.dispatch({ type: MACHINE});
}
render() {
return <AppActivityIndicator />
}
the following is my reducer code which is handling navigation of the screen,
case types.MACHINE:
nextState = DefaultNavigate.router.getStateForAction(
NavigationActions.reset({
index: 1,
actions: [
NavigationActions.navigate({ routeName: 'Home' }),
NavigationActions.navigate({ routeName: 'AppTabNav' })
]
}),
state
);
the following is the render method of MachineList screen in drawer navigator,
render() {
return (
<View style={styles.container}>
<AppStatusBar />
<ListView
initialListSize={10}
dataSource={this.state.dataSource}
renderRow={this.renderRow.bind(this)}
enableEmptySections={true}
/>
</View>
);
}
Please help me out of this one. What am I doing wrong?
dependemcies
"dependencies": {
"native-base": "^2.3.1",
"react": "16.0.0-alpha.12",
"react-devtools": "^2.5.0",
"react-native": "0.47.1",
"react-native-calendars": "^1.5.8",
"react-native-vector-icons": "^4.3.0",
"react-navigation": "^1.0.0-beta.11",
"react-redux": "^5.0.6",
"redux": "^3.7.2",
"redux-logger": "^3.0.6",
"redux-persist": "^4.9.1",
"redux-thunk": "^2.2.0"
},
"devDependencies": {
"babel-jest": "20.0.3",
"babel-preset-react-native": "3.0.0",
"jest": "20.0.4",
"react-test-renderer": "16.0.0-alpha.12"
},
I was facing the same issue. There was a substantial delay while switching the screen. I found this very useful blog https://novemberfive.co/blog/react-performance-navigation-animations/
So the problem was
When a new screen is pushed, React Navigation will initially render it off-screen and animate it into place afterward. This means that when a complex screen with lots of components that easily takes a few hundred milliseconds to render is pushed
To fix this, I used InteractionManager. It basically gives you the callback once all the animation has been completed.
Following is what I have done to avoid delay and app was working fine after the fix. Hope this helps.
// #flow
import React, { Component } from 'react';
import { InteractionManager, ActivityIndicator} from 'react-native';
class Team extends Component<Props> {
state = {
isReady : false
}
componentDidMount() {
// 1: Component is mounted off-screen
InteractionManager.runAfterInteractions(() => {
// 2: Component is done animating
// 3: Start fetching the team / or render the view
// this.props.dispatchTeamFetchStart();
this.setState({
isReady: true
})
});
}
// Render
render() {
if(!this.state.isReady){
return <ActivityIndicator />
}
return(
// Render the complex views
)
...
}
}
export default Team;
I was encountering the same issue with my application. Similar to what other people have described, I have nested Stack and Drawer navigators. The lagginess for me was with the transition between screens in a nested Stack Navigator.
I tried using InteractionManager to resolve this, but it did not seem to make much difference. Eventually I found that building a simple timeout in to delay the rendering of large components made a huge difference.
So, I wrote this simple useIsReady hook which I now use in my screens:
import { useEffect, useState } from "react";
const useIsReady = () => {
const [isReady, setIsReady] = useState(false);
useEffect(() => {
setTimeout(() => setIsReady(true), 100);
}, []);
return isReady;
};
export default useIsReady;
This is how I use the hook in my screens:
import React, { memo } from "react";
import { useTheme } from "react-native-paper";
import { View, ActivityIndicator } from "react-native";
import { useIsReady } from "hooks";
const BusyIndicator = () => {
const theme = useTheme();
return (
<View style={{ flex: 1, justifyContent: "center" }}>
<ActivityIndicator size="large" color={theme.colors.primary} />
</View>
);
};
const Camera = () => {
const isReady = useIsReady();
if (!isReady ) {
return <BusyIndicator />;
}
return (
<> ... </>
);
};
export default memo(Camera);
I have found that this has made the world of difference, and my screen transitions are now completely smooth.
i had same problem, for me it helped using NativeStackNavigator instead of StackNavigator
I was facing the same issue. The following steps helped me greatly decrease lag time:
If you wrapped your components (especially the components in BottomTabBar) with redux's compose where you defined your screens, You will do well to remove that. This will greatly improve the smoothness and speed of transitions.
Just as #Carlos has highlighted above, use InteractionManager.runAfterInteractions(()=>{})
Optimize memory usage and performance (v5)
For those who use version 5 of react navigation
You will need to follow the installation instruction from react-native-screens first. After that using the following snippet before your navigation stacks are rendered (typically in an index.js or App.js file):
// Before rendering any navigation stack
import { enableScreens } from 'react-native-screens';
enableScreens();
I was facing the slow tab navigate issue, The slow issues occur when having nesting things like in my case
I have Drawer from react-navigation/drawer FIRST LEVEL
Second I have Tabs from '#react-navigation/bottom-tabs' under the drawer SECOND LEVEL
The third thing I have a stack (it has multiple screens) from #react-navigation/stack under the Tabs THIRD LEVEL
<Drawer.Navigator>
<Tab.Navigator>
<Stack.Navigator>
<Stack.Screen name="Home"></Stack.Screen>
<Stack.Screen name="Details"></Stack.Screen>
</Stack.Navigator>
<Stack.Navigator>
<Stack.Screen name="Task"></Stack.Screen>
<Stack.Screen name="Details"></Stack.Screen>
</Stack.Navigator>
.....
</Tab.Navigator>
</Drawer.Navigator>
So if I remove any one thing from above the slow issue was gone for example if I remove Drawer.Navigator so I have only two nested things Tab.Navigator and Stack.Navigator so issue was gone.
So what I did I remove Drawer and use a drawer from native base(you can use another drawer package).
<Tab.Navigator>
<Stack.Navigator>
<Stack.Screen name="Home"></Stack.Screen>
<Stack.Screen name="Details"></Stack.Screen>
</Stack.Navigator>
<Stack.Navigator>
<Stack.Screen name="Task"></Stack.Screen>
<Stack.Screen name="Details"></Stack.Screen>
</Stack.Navigator>
.....
</Tab.Navigator>
I know how pain is that I suffering it for a long time and didn't find any solution from the official react-navigation team, Hope Team will fix nesting issue soon, for now, you can use that solution.
Pass a callback function to predefined requestAnimationFrame(no import needed) method and it will automatically invoke that function once the animation is completed.
requestAnimationFrame(() => {
//enter code here
}
I am also facing this issue with stack navigator, Drawer navigator works better..
in stack if there're content of more than 100 lines with some imported component it start's lagging and delay on navigate.
i used in one of my stack screen InteractionManager to render component after animation
it worked, but when i come back on click back, animation shutters/lags
i checked out https://reactnavigation.org/docs/react-native-screens/
as it says react-navigation already uses native navigation component
for disabling and using RN View :-
import {enableScreens} from 'react-native-screens'
enableScreens(false)
i disabled it and now its working way better than native one, dont know why, currently i am in debugging mode, so cant say if it's cause of the remote view...
and really confused why it lags on native navigation while not in RN View
can anyone let me know why its happening? i want to use native navigation

Disable rotation for specific views in react native

How can I disable rotation only for specific views (e.g: when using Navigator) and not for the entire app?
The question here already addresses disabling rotation for the entire app
With the react-native-orientation package, it's possible to lock the orientation to portrait/landscape. The package and documentation can be found here: https://github.com/yamill/react-native-orientation
Remember; you should not put your locking inside the rendering of scenes (nor the renderScene method). Since the Navigator re-renders all the scenes in the route stack, this would probably cause weird side effects for you. Rather, the locking/unlocking should be put in the code that interacts with the route stack (ie. calls the push/pop methods).
If your case is about more specific control over orientations of different screens in StackNavigator (something like Portrait -> LandscapeLeft -> LandscapeRight -> Portrait, and all the way back), here is a may-not-that-pretty solution:
packages needed: react-navigation, react-native-orientation;
define base screens as follow:
// baseScreen.js
import React, { Component } from "react";
import Orientation from "react-native-orientation";
export class PortraitScreen extends Component {
constructor(props) {
super(props);
this._willFocusSubscription = this.props.navigation.addListener("willFocus", payload => {
// lock to portrait when this screen is about to appear
Orientation.lockToPortrait();
})
}
componentWillUnmount() {
// remove subscription when unmount
this._willFocusSubscription.remove();
}
}
export class LandscapeScreen extends Component {
constructor(props) {
super(props);
this._willFocusSubscription = this.props.navigation.addListener("willFocus", payload => {
// lock to landscape
Orientation.lockToLandscape();
})
}
componentWillUnmount() {
// remove subscription either
this._willFocusSubscription.remove();
}
}
define concrete screens which extends abovementioned base screen(s):
// moduleScreens.js
import React from "react";
import { Button, View } from "react-native";
import { PortraitScreen, LandscapeScreen } from "/path/to/baseScreen";
export class VideoDescScreen extends PortraitScreen {
render() {
return (
<View>
<Button
title="watch video"
onPress={() => this.props.navigation.navigate("VideoPlayer")}
/>
</View>
)
}
}
export class VideoPlayerScreen extends LandscapeScreen {
render() {
return <View>...</View>
}
}
create route like this:
// route.js
import React from "react";
import { createStackNavigator } from "react-navigation";
import { VideoDescScreen, VideoPlayerScreen } from "/path/to/moduleScreens";
const stack = createStackNavigator(
{
VideoDesc: {
screen: VideoDescScreen
},
VideoPlayer: {
screen: VideoPlayerScreen
}
}
)
How it works? According to doc, we observe event willFocus when screen is initialized, and each time this screen is about to appear (focused) in navigation, we lock device to our desired orientation, works for both PUSH(to) and POP(back from) behaviors.
Hope it helps.