Snapchat-like Horizontal ScrollView Combined with React Navigation - react-native

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

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>
);
}
}

React native react-navigation drawer not opening

I am using react-navigation as the navigation package for my react native application. and have also installed and configured react-native-gesture-handler along with react-navigation as mentioned in the documentation.
The problem i am facing is that the drawer doesn't open at random times. mostly this occurs when user goes through along the main stack navigation and comes back to home to open the drawer. otherwise the drawer seems to be working without any issues.
this is how i have configured my navigation,
MAIN STACK NAVIGATION
const AppStack = createStackNavigator(
{
DrawerNav: DrawerNav,
Home: Home,
Notification: Notification,
HomeSearch: HomeSearch
}
DRAWER NAVIGATION
const MyDrawerNavigator = createDrawerNavigator(
{
Home: Home,
MyAccount: MyAccount,
ContactUs: ContactUs,
InviteFriend: InviteFriend,
Terms: Terms,
SignOut: SignOut
},
And the MAIN STACK also contains a few TAB STACK also,
I want to know why the drawer doesn't respond.
The Code i used to open the drawer was
this.props.navigation.openDrawer();
bu the above code gave
this.props.navigation.openDrawer() undefined
when ever the above crash i mentioned occurs
as a fix i used,
import { DrawerActions } from "react-navigation";
this.props.navigation.dispatch(DrawerActions.openDrawer())
the above code also stop working after a the user goes through the STACK navigation a few times, but doesn't give any errors on development.
This error occurs both on production as well as development
currently running
react native : 0.59.8
react : 16.8.3
react navigation: 3.9.1,
react-native-gesture-handler:1.1.0,
any help would be much appreciated,
Thanks in advance
Try wrapping all your stack navigation with Drawer navigation.
const StackNav = createStackNavigator({
Home: Home,
Notification: Notification,
HomeSearch: HomeSearch
}
Now wrap the above with Drawer navigation
const AppStack = createDrawerNavigator({
StackNav: {
screen: StackNav,
navigationOptions: {
drawerLabel: <Hidden />,
},
},
Home: Home,
MyAccount: MyAccount,
ContactUs: ContactUs,
InviteFriend: InviteFriend,
Terms: Terms,
SignOut: SignOut
});
Now the StackNav will be showing in the Drawer as one of the screens. So create a class and return null then pass it down to Drawer label.
class Hidden extends Component {
render() {
return null;
}
}
Now you'll be able to call the this.props.navigation.openDrawer(); anywhere on the app. Let me know if it works.
I think you can use another easy way to handle this problem :
You can use react-native-drawer that is available in this link now i'm going to show you how you can work with it :
AppStack :
const AppStack = createStackNavigator(
{
Home: Home,
Notification: Notification,
HomeSearch: HomeSearch
}
Home Navigation
const MyHomeNavigator = createStackNavigator(
{
Home: Home,
MyAccount: MyAccount,
ContactUs: ContactUs,
InviteFriend: InviteFriend,
Terms: Terms,
SignOut: SignOut
},
Now lets assume this is your HomePage :
HomePage
import Drawer from 'react-native-drawer'
import DrawerComponent from '../components/drawer'
export default class HomePage extends Component{
render() {
return (
<Drawer ref={(ref) => this._drawer = ref} content={<DrawerComponent {...this.props}/>} side='right' captureGestures openDrawerOffset={0.3} acceptTap>
//your home page components
</Drawer>
)
}
}
as you can see you can access to the drawer by this._drawer and here i will show you how does <DrawerComponent> like :
DrawerComponent :
export default class DrawerComponent extends React.Component {
navigateTo = (path) => {
this.props.navigation.navigate(path)
}
render(){
return(
<View>
<View>
<Item path='MyAccount' navigate = {this.navigateTo}/>
<Item path='ContactUs' navigate = {this.navigateTo}/>
<Item path='InviteFriend' navigate = {this.navigateTo}/>
//And you add all the item you need here with navigateTo function
</View>
<View>
//LogOut Section
</View>
</View>
)
}
}
This Works for me fine, I hope this works for you too.
Best regards .

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 Navigation - How to pass data across different screens in TabNavigator?

I have a TabNavigator, and in each tab is a StackNavigator. Inside the StackNavigator, I have screens. The screens in each Tab do not call each other directly; the TabNavigator handles the screen changes when a tab is pressed.
In the first tab, if the user clicks a button, some data is created. If the user then navigates to the second Tab, I would like to pass this data to the screen in the second Tab.
Here is a demo of the code:
import React from 'react';
import { Button, Text, View } from 'react-native';
import {
createBottomTabNavigator,
createStackNavigator,
} from 'react-navigation';
class HomeScreen extends React.Component {
doIt = () => {
this.props.navigation.setParams({results: ['one', 'two']}); // <--- set data when user clicks button.
}
render() {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
{/* other code from before here */}
<Button
title="Set Results"
onPress={this.doIt}
/>
</View>
);
}
}
class SettingsScreen extends React.Component {
render() {
console.log(this.props.navigation); // <--- console out when user clicks on this tab
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text>Settings</Text>
</View>
);
}
}
const HomeStack = createStackNavigator({
Home: HomeScreen,
});
const SettingsStack = createStackNavigator({
Settings: SettingsScreen,
});
export default createBottomTabNavigator(
{
Home: HomeStack,
Settings: SettingsStack,
},
{
}
);
The this.props.navigation.state.params never gets the data results in the second Tab. There isn't even a key for it, so if I try to access this.props.navigation.state.params.results, it will be undefined.
This is confusing because I thought props.navigation is passed to all screens automatically.
How can I pass data from one screen to another through the TabNavigator, using just react-navigation? I have seen answers that say to use Redux, but I would not like to import another library if all I want is to keep some state across screens in different react navigators.
It may seem that this.props.navigation.state.params is only able to old one parameter? Possibly? Try this:
doIt = () => {
this.props.navigation.setParams({results: 'one'}); // <--- set data when user clicks button.
}
console.log(this.props.navigation.state.params.results);
Setting props did not work when passing data across different tabs. I even tried playing with AsyncStorage, trying to save and retrieve them in different tabs.
I ended up using Redux to save my states, and that has worked well so far.
I came across a similar problem. I had a multi page form that the client insisted on having each step be enclosed in a tab on a tab bar. I used the react navigation createMaterialTopTabNavigator to create the navigator and couldn't find an easy way to pass the form data between tabs.
What I end up doing was using react's Context API and wrapped the tab navigator in a root form container that provides the context value to the navigator and routes inside. Here is how I did it:
Root form container
// MultiScreenForm.js
imports...
import MultiScreenFormNavigator from './MultiScreenFormNavigator'
export const FormContext = React.createContext()
class MultiScreenForm extends Component {
constructor(props) {
super(props)
this.state = {
// formDataHere
formUpdaters: {
onToggleOptIn: this.handleToggleOptIn // example updater method
// other
}
}
}
handleToggleOptIn = () => {
// toggle opt in form data with this.setState
}
render() {
return (
<FormContext.Provider value={this.state}>
<MultiScreenFormNavigator />
</FormContext.Provider>
)
}
}
export default MultiScreenForm
Example form page
// ProfileForm.js
imports...
import { FormContext } from './MultiScreenForm'
class ProfileForm extends Component {
render() {
// FormContext.Consumer uses function as child pattern
return (
<FormContext.Consumer>
{ (context) => (
// our form can now use anything that we pass through the context
// earlier, we passed the root form's state including an updater
<button onPress={context.formUpdaters.onToggleOptIn} />
// ...
)
}
</FormContext.Consumer>
)
}
}
export default ProfileForm
Tab navigator
// MultiScreenFormNavigator.js
imports...
import ProfileForm from './ProfileForm'
import { createMaterialTopTabNavigator } from 'react-navigation'
const MultiScreenFormNavigator = createMaterialTopTabNavigator(
{
Profile: ProfileForm,
// AnotherForm: AnotherForm
},
// { navigator options here... }
)
export default MultiScreenFormNavigator
We then render the MultiScreenForm instead of the tab navigator directly.
This worked for me but I feel there should be an easier way to do this. I hope people who read this can share their approaches.
#tempomax
tried same with AsyncStorage but data came in with a delay.
Sometimes you don't need Redux if your app stays small.
So tried to find a way without Redux.
Here is what I came up with
I hope it's not too late to answer.
Solved it with NavigationEvents and setting params to Route.
The problem with tab is that you canĀ“t pass params to screen because navigation.navigate will be triggered automatically if createMaterialTopTabNavigator is swiped or clicked on non-active TabBar Button.
This can be solved with NavigationEvent like follow.
import React from 'react';
import { View } from 'react-native';
import { NavigationEvents } from 'react-navigation';
const MyScreen = () => (
<View>
<NavigationEvents
onWillFocus={payload => console.log('will focus',payload)}
onDidFocus={payload => console.log('did focus',payload)}
onWillBlur={payload =>
/*
if screen is about to change this will be triggred
In screen 'MyScreen2' you can get it with navigation.params
*/
this.props.navigation.navigate('MyScreen2', { name: 'Brent' })
}
onDidBlur={payload => console.log('did blur',payload)}
/>
{/*
Your view code
*/}
</View>
);
export default MyScreen;
Now you can get the data in MyScreen2
/* 2. Get the param, provide a fallback value if not available */
const { navigation } = this.props;
const itemId = navigation.getParam('name', 'DefaultName');
const otherParam = navigation.getParam('otherParam', 'some default value');
If you are using React Native Navigation Version 5.x with a DrawerNavigation, you can do this using
in screen 1:
<Button
onPress={() => {
this.props.navigation.navigate(<ScreenNameOfDrawerScreen>,
{screen:'<ScreenNameInTabDrawer>',params:{your_json_Data}});
}} />
in screen 2:
............
render() {
if(this.props.route.params!=undefined){
if(this.props.route.params.your_json_Data!=null){
// Use this.props.route.params.your_json_Data. It is your json data.
}
}
return (
..............

How to invoke App.js from inner component ReactNative?

I'm using StackNavigator to load a Tabbar created with react-native-tab-navigator in App.js. I have a view inside Tabbar. How can I navigate back to App.js from that view?
App.js
import { StackNavigator } from 'react-navigation';
const SigninSignup = StackNavigator({
Signup: { screen: Signup },
Signin: { screen: Signin },
});
render() {
if (this.state.condition) {
return (<Tabbar />);
} else {
return (<SigninSignup />);
}
}
Myview.js (It is inside Tabbar)
How can I go back to App.js and load SigninSignup ?
I have tried
const { navigate } = this.props.navigation;
navigate('Signin', {});
But getting ERROR undefined is not an object (evaluating 'this.props.navigation')
Your navigation hierarchy starts with the SigninSignup navigator.
Since your component is rendering either the navigator or your tab bar according to this.state.condition, you can't just navigate to the other screen.
You con solve this be either moving the tab bar and the SigninSignup navigator into a new root navigator and use that to navigate between everything, or by providing a function to Tabbar that would change the value of condition:
<Tabbar onSomething={() => this.setState({condition: !this.state.condition})} />